diff --git a/.archive/sequence_demo_screenshot.png b/.archive/sequence_demo_screenshot.png deleted file mode 100644 index 8dd48de..0000000 Binary files a/.archive/sequence_demo_screenshot.png and /dev/null differ diff --git a/.archive/sequence_protocol_test.png b/.archive/sequence_protocol_test.png deleted file mode 100644 index 158f93f..0000000 Binary files a/.archive/sequence_protocol_test.png and /dev/null differ diff --git a/README.md b/README.md index 1dd2aad..89be09d 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,30 @@ -# McRogueFace +# McRogueFace - 2D Game Engine -A Python-powered 2D game engine for creating roguelike games, built with C++ and SFML. +An experimental prototype game engine built for my own use in 7DRL 2023. -**Latest Release**: Successfully completed 7DRL 2025 with *"Crypt of Sokoban"* - a unique roguelike that blends Sokoban puzzle mechanics with dungeon crawling! +*Blame my wife for the name* -## Features +## Tenets: -- **Python-First Design**: Write your game logic in Python while leveraging C++ performance -- **Rich UI System**: Sprites, Grids, Frames, and Captions with full animation support -- **Entity-Component Architecture**: Flexible game object system with Python integration -- **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 +* C++ first, Python close behind. +* Entity-Component system based on David Churchill's Memorial University COMP4300 course lectures available on Youtube. +* Graphics, particles and shaders provided by SFML. +* Pathfinding, noise generation, and other Roguelike goodness provided by TCOD. -## Quick Start +## Why? -```bash -# Clone and build -git clone https://github.com/jmcb/McRogueFace.git -cd McRogueFace -make +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. -# Run the example game -cd build -./mcrogueface -``` +## To-do -## Example: Creating a Simple Scene - -```python -import mcrfpy - -# Create a new scene -mcrfpy.createScene("intro") - -# Add a text caption -caption = mcrfpy.Caption(50, 50, "Welcome to McRogueFace!") -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 \ No newline at end of file +* ✅ Initial Commit +* ✅ Integrate scene, action, entity, component system from COMP4300 engine +* ✅ Windows / Visual Studio project +* ✅ Draw Sprites +* ✅ Play Sounds +* ✅ Draw UI, spawn entity from Python code +* ❌ Python AI for entities (NPCs on set paths, enemies towards player) +* ✅ Walking / Collision +* ❌ "Boards" (stairs / doors / walk off edge of screen) +* ❌ Cutscenes - interrupt normal controls, text scroll, character portraits +* ❌ Mouse integration - tooltips, zoom, click to select targets, cursors diff --git a/ROADMAP.md b/ROADMAP.md index 7a4d108..88b0cda 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,54 +1,38 @@ # McRogueFace - Development Roadmap -## Project Status: 🎉 ALPHA 0.1 RELEASE! 🎉 +## Project Status: Post-7DRL 2025 "Crypt of Sokoban" -**Current State**: Alpha release achieved! All critical blockers resolved! -**Latest Update**: Moved RenderTexture (#6) to Beta - Alpha is READY! (2025-07-05) -**Branch**: interpreter_mode (ready for alpha release merge) -**Open Issues**: ~46 remaining (non-blocking quality-of-life improvements) +**Current State**: Successful 7DRL completion with Python/C++ game engine +**Latest Update**: Fixed 12+ critical bugs in one day! (2025-01-03) +**Branch**: interpreter_mode (comprehensive test suite + major stability fixes) +**Open Issues**: ~48 remaining from original 64 (closed 14 + fixed 14 today) --- -## Recent Achievements +## 🎉 TODAY'S ACHIEVEMENTS (2025-01-03) -### 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!** +In a single productive session, we fixed 12+ critical bugs and implemented missing features: -### 2025-07-05: Z-order Rendering Complete! 🎉 -**Issue #63 Resolved**: Consistent z-order rendering with performance optimization -- Dirty flag pattern prevents unnecessary per-frame sorting -- Lazy sorting for both Scene elements and Frame children -- Frame children now respect z_index (fixed inconsistency) -- Automatic dirty marking on z_index changes and collection modifications -- Performance: O(1) check for static scenes vs O(n log n) every frame +### Critical Bug Fixes: +- **Grid Segfault** - Fixed crash when texture is None/null, added default 16x16 cell dimensions +- **Issue #78** - Fixed middle mouse click incorrectly sending 'C' keyboard event (SFML event union bug) +- **Issue #77** - Fixed error message copy/paste bug in Grid validation +- **Issue #74** - Added missing Grid.grid_y property (closes #74) +- **Entity Setters** - Fixed "new style getargs format" error with proper PyVector conversion +- **PyVector** - Implemented missing x/y property getters and setters +- **Sprite Texture** - Fixed setter returning -1 without setting exception +- **keypressScene** - Added validation to reject non-callable arguments -### 2025-07-05: Python Sequence Protocol Complete! 🎉 -**Issue #69 Resolved**: Full sequence protocol implementation for collections -- Complete __setitem__, __delitem__, __contains__ support -- Slice operations with extended slice support (step != 1) -- Concatenation (+) and in-place concatenation (+=) with validation -- 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 +### New Features Implemented: +- **Issue #73** - Entity.index() method for finding position in collection (closes #73) +- **Issue #27** - EntityCollection.extend() for adding multiple entities at once (closes #27) +- **Issue #33** - Sprite index validation against texture bounds (closes #33) +- **Issue #3** - Removed deprecated player_input and turn-based functions (closes #3) +- **Issue #2** - Removed entire registerPyAction/registerInputAction system (closes #2) -### 2025-07-05: Animation System Complete! 🎉 -**Issue #59 Resolved**: Comprehensive animation system with 30+ easing functions -- 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 +### Test-Driven Development: +Every fix was accompanied by a comprehensive test using the timer callback pattern. +All tests verify the fix and ensure no regressions. --- @@ -75,8 +59,6 @@ 2. **Honor system for scripts** - Scripts must return control to C++ render loop 3. **Shared Python state** - All --exec scripts share the same interpreter 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: - `src/McRFPy_Automation.h/cpp` - Complete automation API implementation @@ -108,49 +90,44 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho - #41: UICollection.find(name) method - #38: Frame 'children' constructor parameter - #33: Sprite index validation +- #69: Partial Sequence Protocol (no slicing, 'in' operator) --- -## 🚀 NEXT PHASE: Beta Features & Polish +## 🚧 IMMEDIATE PRIORITY: Critical Bugfixes & Iterator Completion -### Alpha Complete! Moving to Beta Priorities: -1. ~~**#69** - Python Sequence Protocol for collections~~ - *Completed! (2025-07-05)* -2. ~~**#63** - Z-order rendering for UIDrawables~~ - *Completed! (2025-07-05)* -3. ~~**#59** - Animation system~~ - *Completed! (2025-07-05)* -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* +### 🔥 Critical Bugfixes (Complete First) +- [ ] **CRITICAL: Grid Segfault** - Grid class crashes on instantiation (blocks ALL Grid functionality) - *High Priority* +- [ ] **#78** - Middle Mouse Click sends "C" keyboard event to scene event handler - *Confirmed Bug* +- [ ] **#77** - Fix error message copy/paste bug (`x value out of range (0, Grid.grid_y)`) - *Isolated Fix* +- [ ] **#74** - Add missing `Grid.grid_y` property referenced in error messages - *Isolated Fix* - [ ] **#37** - Fix Windows build module import from "scripts" directory - *Isolated Fix* -- [x] **Entity Property Setters** - Fix "new style getargs format" error - *Fixed* -- [x] **Sprite Texture Setter** - Fix "error return without exception set" - *Fixed* -- [x] **keypressScene() Validation** - Add proper error handling - *Fixed* +- [ ] **Entity Property Setters** - Fix "new style getargs format" error - *Multiple Fixes* +- [ ] **Sprite Texture Setter** - Fix "error return without exception set" - *Isolated Fix* +- [ ] **keypressScene() Validation** - Add proper error handling for non-callable arguments - *Isolated Fix* ### 🔄 Complete Iterator System **Status**: Core iterators complete (#72 closed), Grid point iterators still pending - [ ] **Grid Point Iterator Implementation** - Complete the remaining grid iteration work -- [x] **#73** - Add `entity.index()` method for collection removal - *Fixed* -- [x] **#69** ⚠️ **Alpha Blocker** - Refactor all collections to use Python Sequence Protocol - *Completed! (2025-07-05)* +- [ ] **#73** - Add `entity.index()` method for collection removal - *Isolated Fix* +- [ ] **#69** ⚠️ **Alpha Blocker** - Refactor all collections to use Python Sequence Protocol - *Extensive Overhaul* **Dependencies**: Grid point iterators → #73 entity.index() → #69 Sequence Protocol overhaul --- -## ✅ ALPHA 0.1 RELEASE ACHIEVED! (All Blockers Complete) +## 🎯 ALPHA 0.1 RELEASE BLOCKERS (6 Issues) -### ✅ All Alpha Requirements Complete! -- [x] **#69** - Collections use Python Sequence Protocol - *Completed! (2025-07-05)* -- [x] **#63** - Z-order rendering for UIDrawables - *Completed! (2025-07-05)* -- [x] **#59** - Animation system for arbitrary UIDrawable fields - *Completed! (2025-07-05)* -- [x] **#47** - New README.md for Alpha release - *Completed* +### ⚠️ Must Complete Before Alpha Release +- [ ] **#69** - Collections use Python Sequence Protocol - *Extensive Overhaul* +- [ ] **#63** - Z-order rendering for UIDrawables - *Multiple Integrations* +- [ ] **#59** - Animation system for arbitrary UIDrawable fields - *Extensive Overhaul* +- [ ] **#6** - RenderTexture concept for all UIDrawables - *Extensive Overhaul* +- [ ] **#47** - New README.md for Alpha release - *Isolated Fix* - [x] **#3** - Remove deprecated `McRFPy_API::player_input` - *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) @@ -158,8 +135,8 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho ### 🎮 Core Engine Systems #### Iterator/Collection System (2 issues) -- [x] **#73** - Entity index() method for removal - *Fixed* -- [x] **#69** ⚠️ **Alpha Blocker** - Sequence Protocol refactor - *Completed! (2025-07-05)* +- [ ] **#73** - Entity index() method for removal - *Isolated Fix* +- [ ] **#69** ⚠️ **Alpha Blocker** - Sequence Protocol refactor - *Extensive Overhaul* #### Python/C++ Integration (7 issues) - [ ] **#76** - UIEntity derived type preservation in collections - *Multiple Integrations* @@ -172,7 +149,7 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho #### UI/Rendering System (12 issues) - [ ] **#63** ⚠️ **Alpha Blocker** - Z-order for UIDrawables - *Multiple Integrations* -- [x] **#59** ⚠️ **Alpha Blocker** - Animation system - *Completed! (2025-07-05)* +- [ ] **#59** ⚠️ **Alpha Blocker** - Animation system - *Extensive Overhaul* - [ ] **#6** ⚠️ **Alpha Blocker** - RenderTexture for all UIDrawables - *Extensive Overhaul* - [ ] **#10** - UIDrawable visibility/AABB system - *Extensive Overhaul* - [ ] **#8** - UIGrid RenderTexture viewport sizing - *Multiple Integrations* @@ -181,7 +158,7 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho - [ ] **#50** - UIGrid background color field - *Isolated Fix* - [ ] **#19** - Sprite get/set texture methods - *Multiple Integrations* - [ ] **#17** - Move UISprite position into sf::Sprite - *Isolated Fix* -- [x] **#33** - Sprite index validation against texture range - *Fixed* +- [ ] **#33** - Sprite index validation against texture range - *Isolated Fix* #### Grid/Entity System (6 issues) - [ ] **#30** - Entity/Grid association management (.die() method) - *Extensive Overhaul* @@ -206,7 +183,7 @@ Created comprehensive test suite with 13 tests covering all Python-exposed metho - [ ] **#41** - `.find(name)` method for collections - *Multiple Integrations* - [ ] **#38** - `children` arg for Frame initialization - *Isolated Fix* - [ ] **#42** - Click callback arg for UIDrawable init - *Isolated Fix* -- [x] **#27** - UIEntityCollection.extend() method - *Fixed* +- [ ] **#27** - UIEntityCollection.extend() method - *Isolated Fix* - [ ] **#28** - UICollectionIter for scene ui iteration - *Isolated Fix* - [ ] **#26** - UIEntityCollectionIter implementation - *Isolated Fix* @@ -261,8 +238,8 @@ REMAINING IN PHASE 1: 1. Collections Sequence Protocol (#69) - Major refactor, alpha blocker 2. Z-order rendering (#63) - Essential UI improvement, alpha blocker 3. RenderTexture overhaul (#6) - Core rendering improvement, alpha blocker -4. ✅ Animation system (#59) - COMPLETE! 30+ easing functions, all UI properties -5. ✅ Documentation (#47) - README.md complete, #48 dependency docs remaining +4. Animation system (#59) - Major feature, alpha blocker +5. Documentation (#47, #48) - Complete alpha release docs ``` ### Phase 3: Engine Architecture (6-8 weeks) @@ -326,7 +303,7 @@ REMAINING IN PHASE 1: 4. **Multi-Platform**: Windows/Linux feature parity maintained ### Success Metrics for Alpha 0.1 -- [ ] All Alpha Blocker issues resolved (5 of 7 complete: #69, #59, #47, #3, #2) +- [ ] All 7 Alpha Blocker issues resolved - [ ] Grid point iteration complete and tested - [ ] Clean build on Windows and Linux - [ ] Documentation sufficient for external developers @@ -354,9 +331,9 @@ REMAINING IN PHASE 1: --- -*Last Updated: 2025-07-05* -*Total Open Issues: 62* (from original 78) -*Alpha Status: 🎉 COMPLETE! All blockers resolved!* -*Achievement Unlocked: Alpha 0.1 Release Ready* -*Next Phase: Beta features including RenderTexture (#6), advanced UI patterns, and platform polish* +*Last Updated: 2025-07-03* +*Total Open Issues: 64* (from original 78) +*Alpha Blockers: 7* +*Current Work: Python interpreter mode features (--exec flag, automation API)* +*Next Session: Continue interpreter mode or switch to critical bugfixes* diff --git a/src/Animation.cpp b/src/Animation.cpp deleted file mode 100644 index 28f1805..0000000 --- a/src/Animation.cpp +++ /dev/null @@ -1,527 +0,0 @@ -#include "Animation.h" -#include "UIDrawable.h" -#include "UIEntity.h" -#include -#include -#include - -#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; - - if constexpr (std::is_same_v) { - float value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - int value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v>) { - // For sprite animation, get current sprite index - int value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - sf::Color value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - sf::Vector2f value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - 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; - - if constexpr (std::is_same_v) { - float value = 0.0f; - if (target->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - // 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; - - if (currentTarget) { - // Handle UIDrawable targets - if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - } - else if (currentEntityTarget) { - // Handle UIEntity targets - if constexpr (std::is_same_v) { - currentEntityTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - 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; - - if constexpr (std::is_same_v) { - // Interpolate float - const float* start = std::get_if(&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) { - // Interpolate integer - const int* start = std::get_if(&startValue); - if (!start) return target; - - float result; - if (delta) { - result = *start + target * t; - } else { - result = *start + (target - *start) * t; - } - return static_cast(std::round(result)); - } - else if constexpr (std::is_same_v>) { - // 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(t * (target.size() - 1)); - index = std::min(index, target.size() - 1); - return static_cast(target[index]); - } - else if constexpr (std::is_same_v) { - // Interpolate color - const sf::Color* start = std::get_if(&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) { - // Interpolate vector - const sf::Vector2f* start = std::get_if(&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) { - // For text, show characters based on t - const std::string* start = std::get_if(&startValue); - if (!start) return target; - - // If delta mode, append characters from target - if (delta) { - size_t chars = static_cast(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(start->length() * (1.0f - t * 2.0f)); - return start->substr(0, chars); - } else { - // Second half: add characters to target - size_t chars = static_cast(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 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) { - 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& anim) { - return anim->isComplete(); - }), - activeAnimations.end() - ); -} - -void AnimationManager::clear() { - activeAnimations.clear(); -} \ No newline at end of file diff --git a/src/Animation.h b/src/Animation.h deleted file mode 100644 index 6308f32..0000000 --- a/src/Animation.h +++ /dev/null @@ -1,146 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -// Forward declarations -class UIDrawable; -class UIEntity; - -// Forward declare namespace -namespace EasingFunctions { - float linear(float t); -} - -// Easing function type -typedef std::function EasingFunction; - -// Animation target value can be various types -typedef std::variant< - float, // Single float value - int, // Single integer value - std::vector, // 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); - - // Update all animations - void update(float deltaTime); - - // Remove completed animations - void cleanup(); - - // Clear all animations - void clear(); - -private: - AnimationManager() = default; - std::vector> activeAnimations; -}; \ No newline at end of file diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp index a5a195b..8ded69b 100644 --- a/src/GameEngine.cpp +++ b/src/GameEngine.cpp @@ -4,7 +4,6 @@ #include "PyScene.h" #include "UITestScene.h" #include "Resources.h" -#include "Animation.h" GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{}) { @@ -115,18 +114,11 @@ void GameEngine::run() { std::cout << "GameEngine::run() starting main loop..." << std::endl; float fps = 0.0; - frameTime = 0.016f; // Initialize to ~60 FPS clock.restart(); while (running) { currentScene()->update(); testTimers(); - - // Update animations (only if frameTime is valid) - if (frameTime > 0.0f && frameTime < 1.0f) { - AnimationManager::getInstance().update(frameTime); - } - if (!headless) { sUserInput(); } diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index 546857b..4df666e 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -1,11 +1,9 @@ #include "McRFPy_API.h" #include "McRFPy_Automation.h" #include "platform.h" -#include "PyAnimation.h" #include "GameEngine.h" #include "UI.h" #include "Resources.h" -#include "PyScene.h" #include #include @@ -78,9 +76,6 @@ PyObject* PyInit_mcrfpy() /*collections & iterators*/ &PyUICollectionType, &PyUICollectionIterType, &PyUIEntityCollectionType, &PyUIEntityCollectionIterType, - - /*animation*/ - &PyAnimationType, nullptr}; int i = 0; auto t = pytypes[i]; @@ -540,15 +535,3 @@ PyObject* McRFPy_API::_setScale(PyObject* self, PyObject* args) { Py_INCREF(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(scene); - if (pyscene) { - pyscene->ui_elements_need_sort = true; - } - } -} diff --git a/src/McRFPy_API.h b/src/McRFPy_API.h index 4d717df..c714448 100644 --- a/src/McRFPy_API.h +++ b/src/McRFPy_API.h @@ -70,7 +70,4 @@ public: static void executeScript(std::string); static void executePyString(std::string); - - // Helper to mark scenes as needing z_index resort - static void markSceneNeedsSort(); }; diff --git a/src/PyAnimation.cpp b/src/PyAnimation.cpp deleted file mode 100644 index 720b8d9..0000000 --- a/src/PyAnimation.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#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 - -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(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(PyFloat_AsDouble(target_value)); - } - else if (PyLong_Check(target_value)) { - animValue = static_cast(PyLong_AsLong(target_value)); - } - else if (PyList_Check(target_value)) { - // List of integers for sprite animation - std::vector 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(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; - - if constexpr (std::is_same_v) { - return PyFloat_FromDouble(val); - } - else if constexpr (std::is_same_v) { - return PyLong_FromLong(val); - } - else if constexpr (std::is_same_v>) { - // This shouldn't happen as we interpolate to int - return PyLong_FromLong(0); - } - else if constexpr (std::is_same_v) { - return Py_BuildValue("(iiii)", val.r, val.g, val.b, val.a); - } - else if constexpr (std::is_same_v) { - return Py_BuildValue("(ff)", val.x, val.y); - } - else if constexpr (std::is_same_v) { - 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} -}; \ No newline at end of file diff --git a/src/PyAnimation.h b/src/PyAnimation.h deleted file mode 100644 index 9976cb2..0000000 --- a/src/PyAnimation.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include "Common.h" -#include "Python.h" -#include "structmember.h" -#include "Animation.h" -#include - -typedef struct { - PyObject_HEAD - std::shared_ptr 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, - }; -} \ No newline at end of file diff --git a/src/PyScene.cpp b/src/PyScene.cpp index c5ae5d6..35f3ae3 100644 --- a/src/PyScene.cpp +++ b/src/PyScene.cpp @@ -2,7 +2,6 @@ #include "ActionCode.h" #include "Resources.h" #include "PyCallable.h" -#include PyScene::PyScene(GameEngine* g) : Scene(g) { @@ -67,17 +66,8 @@ void PyScene::render() { game->getRenderTarget().clear(); - // Only sort if z_index values have changed - if (ui_elements_need_sort) { - std::sort(ui_elements->begin(), ui_elements->end(), - [](const std::shared_ptr& a, const std::shared_ptr& 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) + auto vec = *ui_elements; + for (auto e: vec) { if (e) e->render(); diff --git a/src/PyScene.h b/src/PyScene.h index 86697ee..068e714 100644 --- a/src/PyScene.h +++ b/src/PyScene.h @@ -14,7 +14,4 @@ public: void render() override final; void do_mouse_input(std::string, std::string); - - // Dirty flag for z_index sorting optimization - bool ui_elements_need_sort = true; }; diff --git a/src/UICaption.cpp b/src/UICaption.cpp index c8c0199..539ec38 100644 --- a/src/UICaption.cpp +++ b/src/UICaption.cpp @@ -3,7 +3,6 @@ #include "PyColor.h" #include "PyVector.h" #include "PyFont.h" -#include UIDrawable* UICaption::click_at(sf::Vector2f point) { @@ -199,7 +198,6 @@ PyGetSetDef UICaption::getsetters[] = { {"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}, {"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} }; @@ -236,7 +234,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) //if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf", // const_cast(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline)) - if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oz|OOOf", + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|zOOOf", const_cast(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline)) { return -1; @@ -252,10 +250,10 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) // check types for font, fill_color, outline_color //std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl; - 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 or None"); + if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){ + PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance"); return -1; - } else if (font != NULL && font != Py_None) + } else if (font != NULL) { auto font_obj = (PyFontObject*)font; self->data->text.setFont(font_obj->data->font); @@ -263,16 +261,8 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) Py_INCREF(font); } else { - // Use default font when None or not provided - 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 - } - } + // default font + //self->data->text.setFont(Resources::game->getFont()); } self->data->text.setString((std::string)text); @@ -304,172 +294,3 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) 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(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(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(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(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(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(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(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(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(std::clamp(value, 0.0f, 255.0f)); - text.setOutlineColor(color); - return true; - } - else if (name == "z_index") { - z_index = static_cast(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(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(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; -} - diff --git a/src/UICaption.h b/src/UICaption.h index 60d8e13..7929f04 100644 --- a/src/UICaption.h +++ b/src/UICaption.h @@ -10,15 +10,6 @@ public: void render(sf::Vector2f, sf::RenderTarget&) override final; PyObjectsEnum derived_type() 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 int set_float_member(PyUICaptionObject* self, PyObject* value, void* closure); diff --git a/src/UICollection.cpp b/src/UICollection.cpp index 28f7df7..1a9b605 100644 --- a/src/UICollection.cpp +++ b/src/UICollection.cpp @@ -6,8 +6,6 @@ #include "UIGrid.h" #include "McRFPy_API.h" #include "PyObjectUtils.h" -#include -#include using namespace mcrfpydef; @@ -150,394 +148,15 @@ 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 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 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 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()); - 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> 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 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 = { .sq_length = (lenfunc)UICollection::len, - .sq_concat = (binaryfunc)UICollection::concat, - .sq_repeat = NULL, .sq_item = (ssizeargfunc)UICollection::getitem, - .was_sq_slice = NULL, - .sq_ass_item = (ssizeobjargproc)UICollection::setitem, - .was_sq_ass_slice = NULL, - .sq_contains = (objobjproc)UICollection::contains, - .sq_inplace_concat = (binaryfunc)UICollection::inplace_concat, - .sq_inplace_repeat = NULL + //.sq_item_by_index = PyUICollection_getitem + //.sq_slice - return a subset of the iterable + //.sq_ass_item - called when `o[x] = y` is executed (x is any object type) + //.sq_ass_slice - cool; no thanks, for now + //.sq_contains - called when `x in o` is executed + //.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer) }; /* Idiomatic way to fetch complete types from the API rather than referencing their PyTypeObject struct @@ -554,12 +173,6 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o) // if not UIDrawable subclass, reject it // 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 if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) && !PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) && @@ -571,45 +184,26 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o) 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"))) { PyUIFrameObject* frame = (PyUIFrameObject*)o; - frame->data->z_index = new_z_index; self->data->push_back(frame->data); } if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) { PyUICaptionObject* caption = (PyUICaptionObject*)o; - caption->data->z_index = new_z_index; self->data->push_back(caption->data); } if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) { PyUISpriteObject* sprite = (PyUISpriteObject*)o; - sprite->data->z_index = new_z_index; self->data->push_back(sprite->data); } if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { PyUIGridObject* grid = (PyUIGridObject*)o; - grid->data->z_index = new_z_index; self->data->push_back(grid->data); } - - // Mark scene as needing resort after adding element - McRFPy_API::markSceneNeedsSort(); Py_INCREF(Py_None); return Py_None; @@ -623,121 +217,27 @@ PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o) return NULL; } long index = PyLong_AsLong(o); - - // Handle negative indexing - while (index < 0) index += self->data->size(); - if (index >= self->data->size()) { PyErr_SetString(PyExc_ValueError, "Index out of range"); 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]; self->data->erase(self->data->begin() + index); - - // Mark scene as needing resort after removing element - McRFPy_API::markSceneNeedsSort(); - Py_INCREF(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 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 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[] = { {"append", (PyCFunction)UICollection::append, METH_O}, //{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO {"remove", (PyCFunction)UICollection::remove, METH_O}, - {"index", (PyCFunction)UICollection::index_method, METH_O}, - {"count", (PyCFunction)UICollection::count, METH_O}, {NULL, NULL, 0, NULL} }; diff --git a/src/UICollection.h b/src/UICollection.h index a1b5d42..886fdd0 100644 --- a/src/UICollection.h +++ b/src/UICollection.h @@ -19,18 +19,9 @@ class UICollection public: static Py_ssize_t len(PyUICollectionObject* self); 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 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* remove(PyUICollectionObject* self, PyObject* o); - static PyObject* index_method(PyUICollectionObject* self, PyObject* value); - static PyObject* count(PyUICollectionObject* self, PyObject* value); static PyMethodDef methods[]; static PyObject* repr(PyUICollectionObject* self); static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds); @@ -80,7 +71,6 @@ namespace mcrfpydef { }, .tp_repr = (reprfunc)UICollection::repr, .tp_as_sequence = &UICollection::sqmethods, - .tp_as_mapping = &UICollection::mpmethods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"), .tp_iter = (getiterfunc)UICollection::iter, diff --git a/src/UIDrawable.cpp b/src/UIDrawable.cpp index 553eaf5..bd4c63d 100644 --- a/src/UIDrawable.cpp +++ b/src/UIDrawable.cpp @@ -4,7 +4,6 @@ #include "UISprite.h" #include "UIGrid.h" #include "GameEngine.h" -#include "McRFPy_API.h" UIDrawable::UIDrawable() { click_callable = NULL; } @@ -81,85 +80,3 @@ void UIDrawable::click_register(PyObject* callable) { click_callable = std::make_unique(callable); } - -PyObject* UIDrawable::get_int(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(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(reinterpret_cast(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(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 -} diff --git a/src/UIDrawable.h b/src/UIDrawable.h index 4ff470f..9832d8d 100644 --- a/src/UIDrawable.h +++ b/src/UIDrawable.h @@ -42,27 +42,6 @@ public: static PyObject* get_click(PyObject* self, 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 { diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp index 2ac1d4d..1a3ce03 100644 --- a/src/UIEntity.cpp +++ b/src/UIEntity.cpp @@ -75,7 +75,7 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { //if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O", // const_cast(keywords), &x, &y, &texture, &sprite_index, &grid)) - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OiO", + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOi|O", const_cast(keywords), &pos, &texture, &sprite_index, &grid)) { return -1; @@ -90,37 +90,33 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { // check types for texture // - // Set Texture - allow None or use default + // Set Texture // - std::shared_ptr texture_ptr = nullptr; - 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"); + if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){ + PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); 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; - } + } /*else if (texture != NULL) // this section needs to go; texture isn't optional and isn't managed by the UI objects anymore + { + self->texture = texture; + Py_INCREF(texture); + } else + { + // default tex? + }*/ if (grid != NULL && !PyObject_IsInstance(grid, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance"); return -1; } + auto pytexture = (PyTextureObject*)texture; if (grid == NULL) self->data = std::make_shared(); else self->data = std::make_shared(*((PyUIGridObject*)grid)->data); // TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers - self->data->sprite = UISprite(texture_ptr, sprite_index, sf::Vector2f(0,0), 1.0); + self->data->sprite = UISprite(pytexture->data, sprite_index, sf::Vector2f(0,0), 1.0); self->data->position = pos_result->data; if (grid != NULL) { PyUIGridObject* pygrid = (PyUIGridObject*)grid; @@ -265,51 +261,3 @@ PyObject* UIEntity::repr(PyUIEntityObject* self) { std::string repr_str = ss.str(); 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(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(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; -} diff --git a/src/UIEntity.h b/src/UIEntity.h index a20953b..8cee8b4 100644 --- a/src/UIEntity.h +++ b/src/UIEntity.h @@ -46,11 +46,6 @@ public: UIEntity(); 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* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored)); static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds); diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp index 40cc74a..f382127 100644 --- a/src/UIFrame.cpp +++ b/src/UIFrame.cpp @@ -51,15 +51,6 @@ void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target) target.draw(box); 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& a, const std::shared_ptr& b) { - return a->z_index < b->z_index; - }); - children_need_sort = false; - } - for (auto drawable : *children) { drawable->render(offset + box.getPosition(), target); } @@ -224,7 +215,6 @@ PyGetSetDef UIFrame::getsetters[] = { {"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}, {"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} }; @@ -274,152 +264,3 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds) if (err_val) return err_val; 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(value), 0, 255); - box.setFillColor(color); - return true; - } else if (name == "fill_color.g") { - auto color = box.getFillColor(); - color.g = std::clamp(static_cast(value), 0, 255); - box.setFillColor(color); - return true; - } else if (name == "fill_color.b") { - auto color = box.getFillColor(); - color.b = std::clamp(static_cast(value), 0, 255); - box.setFillColor(color); - return true; - } else if (name == "fill_color.a") { - auto color = box.getFillColor(); - color.a = std::clamp(static_cast(value), 0, 255); - box.setFillColor(color); - return true; - } else if (name == "outline_color.r") { - auto color = box.getOutlineColor(); - color.r = std::clamp(static_cast(value), 0, 255); - box.setOutlineColor(color); - return true; - } else if (name == "outline_color.g") { - auto color = box.getOutlineColor(); - color.g = std::clamp(static_cast(value), 0, 255); - box.setOutlineColor(color); - return true; - } else if (name == "outline_color.b") { - auto color = box.getOutlineColor(); - color.b = std::clamp(static_cast(value), 0, 255); - box.setOutlineColor(color); - return true; - } else if (name == "outline_color.a") { - auto color = box.getOutlineColor(); - color.a = std::clamp(static_cast(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; -} diff --git a/src/UIFrame.h b/src/UIFrame.h index 2748a1e..986dd1e 100644 --- a/src/UIFrame.h +++ b/src/UIFrame.h @@ -28,7 +28,6 @@ public: sf::RectangleShape box; float outline; std::shared_ptr>> children; - bool children_need_sort = true; // Dirty flag for z_index sorting optimization void render(sf::Vector2f, sf::RenderTarget&) override final; void move(sf::Vector2f); PyObjectsEnum derived_type() override final; @@ -43,15 +42,6 @@ public: static PyGetSetDef getsetters[]; static PyObject* repr(PyUIFrameObject* self); 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 { diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index e13fbcd..7a2f9ed 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -1,7 +1,6 @@ #include "UIGrid.h" #include "GameEngine.h" #include "McRFPy_API.h" -#include UIGrid::UIGrid() {} @@ -219,66 +218,27 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point) int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { int grid_x, grid_y; - PyObject* textureObj = Py_None; + PyObject* textureObj; //float box_x, box_y, box_w, box_h; - PyObject* pos = NULL; - PyObject* size = NULL; + PyObject* pos, *size; //if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) { - if (!PyArg_ParseTuple(args, "ii|OOO", &grid_x, &grid_y, &textureObj, &pos, &size)) { + if (!PyArg_ParseTuple(args, "iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) { return -1; // If parsing fails, return an error } - // Default position and size if not provided - PyVectorObject* pos_result = NULL; - PyVectorObject* size_result = NULL; - - 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* 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; } - if (size) { - size_result = PyVector::from_arg(size); - if (!size_result) - { - 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; - } + PyVectorObject* 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; } // Convert PyObject texture to IndexTexture* @@ -286,7 +246,7 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { std::shared_ptr texture_ptr = nullptr; - // Allow None for texture - use default texture in that case + // Allow None for texture if (textureObj != Py_None) { //if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) { if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) { @@ -295,9 +255,6 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { } PyTextureObject* pyTexture = reinterpret_cast(textureObj); 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 @@ -501,7 +458,6 @@ PyGetSetDef UIGrid::getsetters[] = { {"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 - {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIGRID}, {NULL} /* Sentinel */ }; @@ -625,196 +581,15 @@ 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 = { .sq_length = (lenfunc)UIEntityCollection::len, - .sq_concat = (binaryfunc)UIEntityCollection::concat, - .sq_repeat = NULL, .sq_item = (ssizeargfunc)UIEntityCollection::getitem, - .was_sq_slice = NULL, - .sq_ass_item = (ssizeobjargproc)UIEntityCollection::setitem, - .was_sq_ass_slice = NULL, - .sq_contains = (objobjproc)UIEntityCollection::contains, - .sq_inplace_concat = (binaryfunc)UIEntityCollection::inplace_concat, - .sq_inplace_repeat = NULL + //.sq_item_by_index = UIEntityCollection::getitem + //.sq_slice - return a subset of the iterable + //.sq_ass_item - called when `o[x] = y` is executed (x is any object type) + //.sq_ass_slice - cool; no thanks, for now + //.sq_contains - called when `x in o` is executed + //.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer) }; PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o) @@ -841,29 +616,23 @@ PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject* { if (!PyLong_Check(o)) { - PyErr_SetString(PyExc_TypeError, "EntityCollection.remove requires an integer index to remove"); + PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove"); return NULL; } long index = PyLong_AsLong(o); - - // Handle negative indexing - while (index < 0) index += self->data->size(); - if (index >= self->data->size()) { PyErr_SetString(PyExc_ValueError, "Index out of range"); 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 - self->data->erase(it); + self->data->erase(std::next(self->data->begin(), index)); Py_INCREF(Py_None); return Py_None; } @@ -906,275 +675,10 @@ PyObject* UIEntityCollection::extend(PyUIEntityCollectionObject* self, PyObject* 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 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()); - - // 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> 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[] = { {"append", (PyCFunction)UIEntityCollection::append, METH_O}, {"extend", (PyCFunction)UIEntityCollection::extend, 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} }; @@ -1219,115 +723,3 @@ PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self) Py_DECREF(iterType); 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(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(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; -} diff --git a/src/UIGrid.h b/src/UIGrid.h index a167c0b..1e3f2aa 100644 --- a/src/UIGrid.h +++ b/src/UIGrid.h @@ -45,12 +45,6 @@ public: sf::RenderTexture renderTexture; std::vector points; std::shared_ptr>> 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 PyObject* get_grid_size(PyUIGridObject* self, void* closure); @@ -82,24 +76,15 @@ typedef struct { class UIEntityCollection { public: static PySequenceMethods sqmethods; - static PyMappingMethods mpmethods; static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o); static PyObject* extend(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 PyObject* repr(PyUIEntityCollectionObject* self); static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds); static PyObject* iter(PyUIEntityCollectionObject* self); static Py_ssize_t len(PyUIEntityCollectionObject* self); 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 { @@ -189,7 +174,6 @@ namespace mcrfpydef { }, .tp_repr = (reprfunc)UIEntityCollection::repr, .tp_as_sequence = &UIEntityCollection::sqmethods, - .tp_as_mapping = &UIEntityCollection::mpmethods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"), .tp_iter = (getiterfunc)UIEntityCollection::iter, diff --git a/src/UISprite.cpp b/src/UISprite.cpp index 87b9f2d..b41b9eb 100644 --- a/src/UISprite.cpp +++ b/src/UISprite.cpp @@ -58,7 +58,7 @@ void UISprite::setSpriteIndex(int _sprite_index) sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale()); } -sf::Vector2f UISprite::getScale() const +sf::Vector2f UISprite::getScale() { return sprite.getScale(); } @@ -202,7 +202,6 @@ PyGetSetDef UISprite::getsetters[] = { {"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}, {"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} }; @@ -225,8 +224,8 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds) //std::cout << "Init called\n"; static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr }; float x = 0.0f, y = 0.0f, scale = 1.0f; - int sprite_index = 0; - PyObject* texture = NULL; + int sprite_index; + PyObject* texture; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif", const_cast(keywords), &x, &y, &texture, &sprite_index, &scale)) @@ -234,107 +233,15 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds) return -1; } - // Handle texture - allow None or use default - std::shared_ptr texture_ptr = nullptr; - 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; - } 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"); + // check types for texture + //if (texture != NULL && !PyObject_IsInstance(texture, (PyObject*)&PyTextureType)){ + if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){ + PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); return -1; } - - self->data = std::make_shared(texture_ptr, sprite_index, sf::Vector2f(x, y), scale); + auto pytexture = (PyTextureObject*)texture; + self->data = std::make_shared(pytexture->data, sprite_index, sf::Vector2f(x, y), scale); self->data->setPosition(sf::Vector2f(x, y)); 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(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(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; -} diff --git a/src/UISprite.h b/src/UISprite.h index 0082ccf..0b172c6 100644 --- a/src/UISprite.h +++ b/src/UISprite.h @@ -33,7 +33,7 @@ public: void setPosition(sf::Vector2f); sf::Vector2f getPosition(); void setScale(sf::Vector2f); - sf::Vector2f getScale() const; + sf::Vector2f getScale(); void setSpriteIndex(int); int getSpriteIndex(); @@ -41,12 +41,6 @@ public: std::shared_ptr getTexture(); 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); diff --git a/src/main.cpp b/src/main.cpp index e0e9835..1b97c49 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,8 +3,6 @@ #include "CommandLineParser.h" #include "McRogueFaceConfig.h" #include "McRFPy_API.h" -#include "PyFont.h" -#include "PyTexture.h" #include #include #include @@ -46,27 +44,14 @@ int run_game_engine(const McRogueFaceConfig& config) int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[]) { - // Create a game engine with the requested configuration - GameEngine* engine = new GameEngine(config); + // Create a headless game engine for automation API support + McRogueFaceConfig engine_config = config; + engine_config.headless = true; // Force headless mode for Python interpreter + GameEngine* engine = new GameEngine(engine_config); // Initialize Python with configuration 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("assets/JetbrainsMono.ttf"); - McRFPy_API::default_texture = std::make_shared("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 if (!config.python_command.empty()) { // Execute command from -c @@ -176,9 +161,6 @@ int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv PyRun_InteractiveLoop(stdin, ""); } - // Run the game engine after script execution - engine->run(); - Py_Finalize(); delete engine; return result; diff --git a/tests/animation_demo.py b/tests/animation_demo.py deleted file mode 100644 index f12fc70..0000000 --- a/tests/animation_demo.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/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.") \ No newline at end of file diff --git a/.archive/entity_property_setters_test.py b/tests/entity_property_setters_test.py similarity index 100% rename from .archive/entity_property_setters_test.py rename to tests/entity_property_setters_test.py diff --git a/.archive/entity_setter_simple_test.py b/tests/entity_setter_simple_test.py similarity index 100% rename from .archive/entity_setter_simple_test.py rename to tests/entity_setter_simple_test.py diff --git a/tests/generate_caption_screenshot_fixed.py b/tests/generate_caption_screenshot_fixed.py deleted file mode 100644 index 66234cb..0000000 --- a/tests/generate_caption_screenshot_fixed.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/tests/generate_docs_screenshots.py b/tests/generate_docs_screenshots.py deleted file mode 100755 index 53393fd..0000000 --- a/tests/generate_docs_screenshots.py +++ /dev/null @@ -1,451 +0,0 @@ -#!/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...") \ No newline at end of file diff --git a/tests/generate_docs_screenshots_simple.py b/tests/generate_docs_screenshots_simple.py deleted file mode 100755 index 75712f4..0000000 --- a/tests/generate_docs_screenshots_simple.py +++ /dev/null @@ -1,217 +0,0 @@ -#!/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...") \ No newline at end of file diff --git a/tests/generate_entity_screenshot_fixed.py b/tests/generate_entity_screenshot_fixed.py deleted file mode 100644 index 4855319..0000000 --- a/tests/generate_entity_screenshot_fixed.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/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) diff --git a/tests/generate_grid_screenshot.py b/tests/generate_grid_screenshot.py deleted file mode 100644 index 706b704..0000000 --- a/tests/generate_grid_screenshot.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/tests/generate_sprite_screenshot.py b/tests/generate_sprite_screenshot.py deleted file mode 100644 index 3a314bb..0000000 --- a/tests/generate_sprite_screenshot.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/.archive/issue27_entity_extend_test.py b/tests/issue27_entity_extend_test.py similarity index 100% rename from .archive/issue27_entity_extend_test.py rename to tests/issue27_entity_extend_test.py diff --git a/.archive/issue33_sprite_index_validation_test.py b/tests/issue33_sprite_index_validation_test.py similarity index 100% rename from .archive/issue33_sprite_index_validation_test.py rename to tests/issue33_sprite_index_validation_test.py diff --git a/.archive/issue73_entity_index_test.py b/tests/issue73_entity_index_test.py similarity index 100% rename from .archive/issue73_entity_index_test.py rename to tests/issue73_entity_index_test.py diff --git a/.archive/issue73_simple_index_test.py b/tests/issue73_simple_index_test.py similarity index 100% rename from .archive/issue73_simple_index_test.py rename to tests/issue73_simple_index_test.py diff --git a/.archive/issue74_grid_xy_properties_test.py b/tests/issue74_grid_xy_properties_test.py similarity index 100% rename from .archive/issue74_grid_xy_properties_test.py rename to tests/issue74_grid_xy_properties_test.py diff --git a/.archive/issue78_middle_click_fix_test.py b/tests/issue78_middle_click_fix_test.py similarity index 100% rename from .archive/issue78_middle_click_fix_test.py rename to tests/issue78_middle_click_fix_test.py diff --git a/tests/simple_screenshot_test.py b/tests/simple_screenshot_test.py deleted file mode 100644 index 42815a4..0000000 --- a/tests/simple_screenshot_test.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/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...") \ No newline at end of file diff --git a/.archive/sprite_texture_setter_test.py b/tests/sprite_texture_setter_test.py similarity index 100% rename from .archive/sprite_texture_setter_test.py rename to tests/sprite_texture_setter_test.py diff --git a/tests/ui_Entity_issue73_test.py b/tests/ui_Entity_issue73_test.py index 7f2b3cd..f843cbb 100644 --- a/tests/ui_Entity_issue73_test.py +++ b/tests/ui_Entity_issue73_test.py @@ -33,34 +33,34 @@ def test_Entity(): # Test entity properties try: - print(f" Entity1 pos: {entity1.pos}") - print(f" Entity1 draw_pos: {entity1.draw_pos}") - print(f" Entity1 sprite_number: {entity1.sprite_number}") + print(f"✓ Entity1 pos: {entity1.pos}") + print(f"✓ Entity1 draw_pos: {entity1.draw_pos}") + print(f"✓ Entity1 sprite_number: {entity1.sprite_number}") # Modify properties entity1.pos = mcrfpy.Vector(3, 3) entity1.sprite_number = 5 - print(" Entity properties modified") + print("✓ Entity properties modified") except Exception as e: - print(f"X Entity property access failed: {e}") + print(f"✗ Entity property access failed: {e}") # Test gridstate access try: gridstate = entity2.gridstate - print(" Entity gridstate accessible") + print(f"✓ Entity gridstate accessible") # Test at() method - point_state = entity2.at()#.at(0, 0) - print(" Entity at() method works") + point_state = entity2.at(0, 0) + print(f"✓ Entity at() method works") except Exception as e: - print(f"X Entity gridstate/at() failed: {e}") + print(f"✗ Entity gridstate/at() failed: {e}") # Test index() method (Issue #73) print("\nTesting index() method (Issue #73)...") try: # Try to find entity2's 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 if entities[index] == entity2: @@ -70,7 +70,7 @@ def test_Entity(): # Remove using 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: print("✗ index() method not implemented (Issue #73)") # Try manual removal as workaround @@ -78,21 +78,21 @@ def test_Entity(): for i in range(len(entities)): if entities[i] == entity2: entities.remove(i) - print(":) Manual removal workaround succeeded") + print(f"✓ Manual removal workaround succeeded") break except: print("✗ Manual removal also failed") except Exception as e: - print(f":) index() method error: {e}") + print(f"✗ index() method error: {e}") # Test EntityCollection iteration try: positions = [] for entity in entities: positions.append(entity.pos) - print(f":) Entity iteration works: {len(positions)} entities") + print(f"✓ Entity iteration works: {len(positions)} entities") except Exception as e: - print(f"X Entity iteration failed: {e}") + print(f"✗ Entity iteration failed: {e}") # Test EntityCollection extend (Issue #27) try: @@ -101,11 +101,11 @@ def test_Entity(): mcrfpy.Entity(mcrfpy.Vector(9, 9), mcrfpy.default_texture, 4, grid) ] entities.extend(new_entities) - print(f":) extend() method works: now {len(entities)} entities") + print(f"✓ extend() method works: now {len(entities)} entities") except AttributeError: print("✗ extend() method not implemented (Issue #27)") except Exception as e: - print(f"X extend() method error: {e}") + print(f"✗ extend() method error: {e}") # Skip screenshot in headless mode print("PASS") @@ -113,4 +113,4 @@ def test_Entity(): # Run test immediately in headless mode print("Running test immediately...") test_Entity() -print("Test completed.") +print("Test completed.") \ No newline at end of file