diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index bf88b73..546857b 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -5,6 +5,7 @@ #include "GameEngine.h" #include "UI.h" #include "Resources.h" +#include "PyScene.h" #include #include @@ -539,3 +540,15 @@ 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 c714448..4d717df 100644 --- a/src/McRFPy_API.h +++ b/src/McRFPy_API.h @@ -70,4 +70,7 @@ 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/PyScene.cpp b/src/PyScene.cpp index 382ac60..c5ae5d6 100644 --- a/src/PyScene.cpp +++ b/src/PyScene.cpp @@ -67,17 +67,17 @@ void PyScene::render() { game->getRenderTarget().clear(); - // Create a copy of the vector to sort by z_index - auto vec = *ui_elements; + // 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; + } - // Sort by z_index (lower values rendered first) - std::sort(vec.begin(), vec.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) { - return a->z_index < b->z_index; - }); - - // Render in sorted order - for (auto e: vec) + // Render in sorted order (no need to copy anymore) + for (auto e: *ui_elements) { if (e) e->render(); diff --git a/src/PyScene.h b/src/PyScene.h index 068e714..86697ee 100644 --- a/src/PyScene.h +++ b/src/PyScene.h @@ -14,4 +14,7 @@ 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/UICollection.cpp b/src/UICollection.cpp index d39e815..28f7df7 100644 --- a/src/UICollection.cpp +++ b/src/UICollection.cpp @@ -210,6 +210,9 @@ int UICollection::setitem(PyUICollectionObject* self, Py_ssize_t index, PyObject // Replace the element (*vec)[index] = new_drawable; + // Mark scene as needing resort after replacing element + McRFPy_API::markSceneNeedsSort(); + return 0; } @@ -426,6 +429,10 @@ int UICollection::ass_subscript(PyUICollectionObject* self, PyObject* key, PyObj // 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 @@ -502,6 +509,9 @@ int UICollection::ass_subscript(PyUICollectionObject* self, PyObject* key, PyObj } } + // Mark scene as needing resort after slice assignment + McRFPy_API::markSceneNeedsSort(); + return 0; } } else { @@ -597,6 +607,9 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* 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; @@ -622,6 +635,10 @@ PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o) // 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; } diff --git a/src/UIDrawable.cpp b/src/UIDrawable.cpp index 1bee9de..553eaf5 100644 --- a/src/UIDrawable.cpp +++ b/src/UIDrawable.cpp @@ -4,6 +4,7 @@ #include "UISprite.h" #include "UIGrid.h" #include "GameEngine.h" +#include "McRFPy_API.h" UIDrawable::UIDrawable() { click_callable = NULL; } @@ -142,6 +143,23 @@ int UIDrawable::set_int(PyObject* self, PyObject* value, void* closure) { 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 44e647c..4ff470f 100644 --- a/src/UIDrawable.h +++ b/src/UIDrawable.h @@ -48,6 +48,9 @@ public: // 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; } diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp index cd59cad..40cc74a 100644 --- a/src/UIFrame.cpp +++ b/src/UIFrame.cpp @@ -51,6 +51,15 @@ 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); } diff --git a/src/UIFrame.h b/src/UIFrame.h index 6613f80..2748a1e 100644 --- a/src/UIFrame.h +++ b/src/UIFrame.h @@ -28,6 +28,7 @@ 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; diff --git a/tests/generate_entity_screenshot_fixed.py b/tests/generate_entity_screenshot_fixed.py index 2f6f433..4855319 100644 --- a/tests/generate_entity_screenshot_fixed.py +++ b/tests/generate_entity_screenshot_fixed.py @@ -23,17 +23,22 @@ mcrfpy.createScene("entities") # We use: mcrfpy.default_font (which is already loaded by the engine) # Title -title = mcrfpy.Caption(400, 30, "Entity Example - Roguelike Characters") -title.font = mcrfpy.default_font -title.font_size = 24 -title.font_color = (255, 255, 255) +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, 32, 32) -grid.texture = texture +#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 @@ -42,51 +47,50 @@ WALL = 11 # Stone wall # Fill with floor for x in range(20): for y in range(15): - grid.set_tile(x, y, FLOOR) + grid.at((x, y)).tilesprite = WALL # Add walls around edges for x in range(20): - grid.set_tile(x, 0, WALL) - grid.set_tile(x, 14, WALL) + grid.at((x, 0)).tilesprite = WALL + grid.at((x, 14)).tilesprite = WALL for y in range(15): - grid.set_tile(0, y, WALL) - grid.set_tile(19, y, WALL) + grid.at((0, y)).tilesprite = WALL + grid.at((19, y)).tilesprite = WALL # Create entities # Player at center -player = mcrfpy.Entity(10, 7) -player.texture = texture -player.sprite_index = 84 # Player sprite +player = mcrfpy.Entity((10, 7), t, 84) +#player.texture = texture +#player.sprite_index = 84 # Player sprite # Enemies -rat1 = mcrfpy.Entity(5, 5) -rat1.texture = texture -rat1.sprite_index = 123 # Rat +rat1 = mcrfpy.Entity((5, 5), t, 123) +#rat1.texture = texture +#rat1.sprite_index = 123 # Rat -rat2 = mcrfpy.Entity(15, 5) -rat2.texture = texture -rat2.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) -big_rat.texture = texture -big_rat.sprite_index = 130 # Big 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) -cyclops.texture = texture -cyclops.sprite_index = 109 # Cyclops +cyclops = mcrfpy.Entity((13, 10), t, 109) +#cyclops.texture = texture +#cyclops.sprite_index = 109 # Cyclops # Items -chest = mcrfpy.Entity(3, 3) -chest.texture = texture -chest.sprite_index = 89 # Chest +chest = mcrfpy.Entity((3, 3), t, 89) +#chest.texture = texture +#chest.sprite_index = 89 # Chest -boulder = mcrfpy.Entity(10, 5) -boulder.texture = texture -boulder.sprite_index = 66 # Boulder - -key = mcrfpy.Entity(17, 12) -key.texture = texture -key.sprite_index = 384 # Key +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) @@ -99,29 +103,29 @@ 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) +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) +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_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_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) +#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") @@ -131,10 +135,10 @@ ui.append(entity_label) ui.append(info) ui.append(legend_frame) ui.append(legend_title) -ui.append(legend_text) +#ui.append(legend_text) # Switch to scene mcrfpy.setScene("entities") # Set timer to capture after rendering starts -mcrfpy.setTimer("capture", capture_entity, 100) \ No newline at end of file +mcrfpy.setTimer("capture", capture_entity, 100) diff --git a/tests/ui_Entity_issue73_test.py b/tests/ui_Entity_issue73_test.py index f843cbb..7f2b3cd 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"✗ Entity property access failed: {e}") + print(f"X Entity property access failed: {e}") # Test gridstate access try: gridstate = entity2.gridstate - print(f"✓ Entity gridstate accessible") + print(" Entity gridstate accessible") # Test at() method - point_state = entity2.at(0, 0) - print(f"✓ Entity at() method works") + point_state = entity2.at()#.at(0, 0) + print(" Entity at() method works") except Exception as e: - print(f"✗ Entity gridstate/at() failed: {e}") + print(f"X 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(f"✓ Manual removal workaround succeeded") + print(":) 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"✗ Entity iteration failed: {e}") + print(f"X 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"✗ extend() method error: {e}") + print(f"X 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.") \ No newline at end of file +print("Test completed.")