diff --git a/src/PyTexture.cpp b/src/PyTexture.cpp index 8f8d4df..eb4bfad 100644 --- a/src/PyTexture.cpp +++ b/src/PyTexture.cpp @@ -28,36 +28,12 @@ sf::Sprite PyTexture::sprite(int index, sf::Vector2f pos, sf::Vector2f s) PyObject* PyTexture::pyObject() { - // method 1: works but with type weirdness - //PyObject* obj = PyType_GenericAlloc(&mcrfpydef::PyTextureType, 0); - //Py_SET_TYPE(obj, &mcrfpydef::PyTextureType); - - // method 2: does not work (segfault on use of the mcrfpy.Texture object) - //PyObject* obj = PyTexture::pynew(&mcrfpydef::PyTextureType, Py_None, Py_None); - - // method 3: does not work (segfault on use of the mcrfpy.Texture object) std::cout << "Find type" << std::endl; auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"); - //auto type = obj->ob_type; - //auto type = &mcrfpydef::PyTextureType; - //std::cout << "assigned value 0x" << std::hex << reinterpret_cast(type) << std::endl; - //std::cout << "Found PyTextureType: " << PyUnicode_AsUTF8(PyObject_Repr((PyObject*)type)) << std::endl; - //std::cout << "PyTextureType metatype: " << PyUnicode_AsUTF8(PyObject_Repr((PyObject_Type((PyObject*)type)))) << std::endl; - //std::cout << "tp_alloc: 0x" << std::hex << reinterpret_cast (type->tp_alloc) << std::endl << - // "tp_new: 0x" << std::hex << reinterpret_cast(type->tp_new) << std::endl; - //PyObject* obj = ((PyTypeObject*)type)->tp_new((PyTypeObject*)type, Py_None, Py_None); - PyObject* obj = PyTexture::pynew(type, Py_None, Py_None); - //Py_SET_TYPE(obj, type); + PyObject* obj = PyTexture::pynew(type, Py_None, Py_None); - // method 4: call the type object? - - std::cout << "Instantiated" << std::endl; - //Py_SET_TYPE(obj, &mcrfpydef::PyTextureType); - - //PyObject_CallFunction(&mcrfpydef::PyTextureType, try { ((PyTextureObject*)obj)->data = shared_from_this(); - std::cout << "Sideloaded texture: " << PyUnicode_AsUTF8(PyObject_Repr(obj)) << std::endl; } catch (std::bad_weak_ptr& e) { diff --git a/src/UI.cpp b/src/UI.cpp deleted file mode 100644 index 5d43918..0000000 --- a/src/UI.cpp +++ /dev/null @@ -1,527 +0,0 @@ -#include "UI.h" -#include "Resources.h" -#include "GameEngine.h" - -/* //callability fields & methods - PyObject* click_callable; - virtual UIDrawable* click_at(sf::Vector2f point); - void click_register(PyObject*); - void click_unregister(); -*/ - -UIDrawable::UIDrawable() { click_callable = NULL; } - -UIDrawable* UIFrame::click_at(sf::Vector2f point) -{ - for (auto e: *children) - { - auto p = e->click_at(point + box.getPosition()); - if (p) - return p; - } - if (click_callable) - { - float x = box.getPosition().x, y = box.getPosition().y, w = box.getSize().x, h = box.getSize().y; - if (point.x > x && point.y > y && point.x < x+w && point.y < y+h) return this; - } - return NULL; -} - -UIDrawable* UICaption::click_at(sf::Vector2f point) -{ - if (click_callable) - { - if (text.getGlobalBounds().contains(point)) return this; - } - return NULL; -} - -UIDrawable* UISprite::click_at(sf::Vector2f point) -{ - if (click_callable) - { - if(sprite.getGlobalBounds().contains(point)) return this; - } - return NULL; -} - -UIDrawable* UIGrid::click_at(sf::Vector2f point) -{ - if (click_callable) - { - if(box.getGlobalBounds().contains(point)) return this; - } - return NULL; -} - -void UIDrawable::click_register(PyObject* callable) -{ - /* - if (click_callable) - { - // decrement reference before overwriting - Py_DECREF(click_callable); - } - click_callable = callable; - Py_INCREF(click_callable); - */ - click_callable = std::make_unique(callable); -} - -void UIDrawable::click_unregister() -{ - /* - if (click_callable == NULL) return; - Py_DECREF(click_callable); - click_callable = NULL; - */ - click_callable.reset(); -} - -void UIDrawable::render() -{ - //std::cout << "Rendering base UIDrawable\n"; - render(sf::Vector2f()); -} -UIFrame::UIFrame(): -//x(0), y(0), w(0), h(0), -outline(0) -{ - children = std::make_shared>>(); - box.setPosition(0, 0); - box.setSize(sf::Vector2f(0, 0)); - /* - pyOutlineColor = NULL; - pyFillColor = NULL; - _outlineColor = NULL; - _fillColor = NULL; - */ -} - -UIFrame::UIFrame(float _x, float _y, float _w, float _h): -//x(_x), y(_y), w(_w), h(_h), -outline(0) -{ - box.setPosition(_x, _y); - box.setSize(sf::Vector2f(_w, _h)); - children = std::make_shared>>(); - /* - pyOutlineColor = NULL; - pyFillColor = NULL; - _outlineColor = NULL; - _fillColor = NULL; - */ -} - -UIFrame::~UIFrame() -{ - children.reset(); - /* - if (pyOutlineColor) Py_DECREF(pyOutlineColor); - else if (_outlineColor) delete _outlineColor; - if (pyFillColor) Py_DECREF(pyFillColor); - else if (_fillColor) delete _fillColor; - */ -} - -/* - sf::Color& fillColor(); // getter - void fillColor(sf::Color c); // C++ setter - void fillColor(PyObject* pyColor); // Python setter - - sf::Color& outlineColor(); // getter - void outlineColor(sf::Color c); // C++ setter - void outlineColor(PyObject* pyColor); // Python setter -*/ - - -PyObjectsEnum UIFrame::derived_type() -{ - return PyObjectsEnum::UIFRAME; -} - -void UIFrame::render(sf::Vector2f offset) -{ - //std::cout << "Rendering UIFrame w/ offset " << offset.x << ", " << offset.y << "\n"; - //std::cout << "position = " << x << ", " << y << "\n"; - box.move(offset); - Resources::game->getWindow().draw(box); - box.move(-offset); - //sf::RectangleShape box = sf::RectangleShape(sf::Vector2f(w,h)); - //sf::Vector2f pos = sf::Vector2f(x, y); - //box.setPosition(offset + pos); - //if (_fillColor) { box.setFillColor(fillColor()); } - //if (_outlineColor) { box.setOutlineColor(outlineColor()); } - //box.setOutlineThickness(outline); - //Resources::game->getWindow().draw(box); - for (auto drawable : *children) { - drawable->render(offset + box.getPosition()); - } -} - -void UICaption::render(sf::Vector2f offset) -{ - //std::cout << "Rendering Caption with offset\n"; - text.move(offset); - Resources::game->getWindow().draw(text); - text.move(-offset); -} - -UISprite::UISprite() {} -/* - // * tearing down the old IndexTexture way of life -UISprite::UISprite(IndexTexture* _itex, int _sprite_index, float x = 0.0, float y = 0.0, float s = 1.0) -: itex(_itex), sprite_index(_sprite_index) -{ - sprite.setTexture(_itex->texture); - sprite.setTextureRect(_itex->spriteCoordinates(_sprite_index)); - sprite.setPosition(sf::Vector2f(x, y)); - sprite.setScale(sf::Vector2f(s, s)); -} - -UISprite::UISprite(IndexTexture* _itex, int _sprite_index, sf::Vector2f pos, float s = 1.0) -: itex(_itex), sprite_index(_sprite_index) -{ - sprite.setTexture(_itex->texture); - sprite.setTextureRect(_itex->spriteCoordinates(_sprite_index)); - sprite.setPosition(pos); - sprite.setScale(sf::Vector2f(s, s)); -} -*/ - -UISprite::UISprite(std::shared_ptr _ptex, int _sprite_index, sf::Vector2f _pos, float _scale) -: ptex(_ptex), sprite_index(_sprite_index) -{ - sprite = ptex->sprite(sprite_index, _pos, sf::Vector2f(_scale, _scale)); -} - -//void UISprite::update() -//{ - //auto& tex = Resources::game->textures[texture_index]; - //sprite.setTexture(tex.texture); - //sprite.setScale(sf::Vector2f(scale, scale)); - //sprite.setPosition(sf::Vector2f(x, y)); - //std::cout << "Drawable position: " << x << ", " << y << " -> " << s.getPosition().x << ", " << s.getPosition().y << std::endl; - //sprite.setTextureRect(tex.spriteCoordinates(sprite_index)); -//} - -void UISprite::render(sf::Vector2f offset) -{ - sprite.move(offset); - Resources::game->getWindow().draw(sprite); - sprite.move(-offset); -} - -// 7DRL hack; needed to draw entities to UIGrid. TODO, apply this technique to all UIDrawables -void UISprite::render(sf::Vector2f offset, sf::RenderTexture& target) -{ - sprite.move(offset); - target.draw(sprite); - sprite.move(-offset); -} - -/* -void UISprite::setPosition(float x, float y) -{ - setPosition(sf::Vector2f(x, y)); -} -*/ - -void UISprite::setPosition(sf::Vector2f pos) -{ - sprite.setPosition(pos); -} - -void UISprite::setScale(sf::Vector2f s) -{ - sprite.setScale(s); -} - -void UISprite::setTexture(std::shared_ptr _ptex, int _sprite_index) -{ - ptex = _ptex; - if (_sprite_index != -1) // if you are changing textures, there's a good chance you need a new index too - sprite_index = _sprite_index; - sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale()); -} - -void UISprite::setSpriteIndex(int _sprite_index) -{ - sprite_index = _sprite_index; - sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale()); -} - -sf::Vector2f UISprite::getScale() -{ - return sprite.getScale(); -} - -sf::Vector2f UISprite::getPosition() -{ - return sprite.getPosition(); -} - -std::shared_ptr UISprite::getTexture() -{ - return ptex; -} - -int UISprite::getSpriteIndex() -{ - return sprite_index; -} - -PyObjectsEnum UICaption::derived_type() -{ - return PyObjectsEnum::UICAPTION; -} - -PyObjectsEnum UISprite::derived_type() -{ - return PyObjectsEnum::UISPRITE; -} - -// UIGrid support classes' methods - -UIGridPoint::UIGridPoint() -:color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false), - tilesprite(-1), tile_overlay(-1), uisprite(-1) -{ -} - -UIEntity::UIEntity() {} // this will not work lol. TODO remove default constructor by finding the shared pointer inits that use it - -UIEntity::UIEntity(UIGrid& grid) -: gridstate(grid.grid_x * grid.grid_y) -{ -} - -// UIGrid methods - -UIGrid::UIGrid() -{ -} - -/* -UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, float _x, float _y, float _w, float _h) -: grid_x(gx), grid_y(gy), - zoom(1.0f), center_x((gx/2) * _ptex->sheet_width), center_y((gy/2) * _ptex->sheet_height), - itex(_itex), points(gx * gy) -{ - // set up blank list of entities - entities = std::make_shared>>(); - - box.setSize(sf::Vector2f(_w, _h)); - box.setPosition(sf::Vector2f(_x, _y)); - - box.setFillColor(sf::Color(0,0,0,0)); - renderTexture.create(_w, _h); - sprite.setTexture(_itex->texture); - output.setTextureRect( - sf::IntRect(0, 0, - box.getSize().x, box.getSize().y)); - output.setPosition(box.getPosition()); - // textures are upside-down inside renderTexture - output.setTexture(renderTexture.getTexture()); -} -*/ - -UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, sf::Vector2f _xy, sf::Vector2f _wh) -: grid_x(gx), grid_y(gy), - zoom(1.0f), center_x((gx/2) * _ptex->sprite_width), center_y((gy/2) * _ptex->sprite_height), - ptex(_ptex), points(gx * gy) -{ - // set up blank list of entities - entities = std::make_shared>>(); - - box.setSize(_wh); - box.setPosition(_xy); - - box.setFillColor(sf::Color(0,0,0,0)); - //renderTexture.create(_wh.x, _wh.y); - // create renderTexture with maximum theoretical size; sprite can resize to show whatever amount needs to be rendered - renderTexture.create(1920, 1080); // TODO - renderTexture should be window size; above 1080p this will cause rendering errors - - //sprite.setTexture(_itex->texture); - sprite = ptex->sprite(0); - - output.setTextureRect( - sf::IntRect(0, 0, - box.getSize().x, box.getSize().y)); - output.setPosition(box.getPosition()); - // textures are upside-down inside renderTexture - output.setTexture(renderTexture.getTexture()); - -} - -void UIGrid::update() -{ -} - -/* -void UIGrid::setSprite(int ti) -{ - //int tx = ti % itex->grid_width, ty = ti / itex->grid_width; - // sprite.setTextureRect(sf::IntRect(tx * itex->grid_size, ty * itex->grid_size, itex->grid_size, itex->grid_size)); - sprite = ptex->sprite(ti); -} -*/ - -void UIGrid::render(sf::Vector2f) -{ - output.setPosition(box.getPosition()); // output sprite can move; update position when drawing - // output size can change; update size when drawing - output.setTextureRect( - sf::IntRect(0, 0, - box.getSize().x, box.getSize().y)); - renderTexture.clear(sf::Color(8, 8, 8, 255)); // TODO - UIGrid needs a "background color" field - // sprites that are visible according to zoom, center_x, center_y, and box width - float center_x_sq = center_x / ptex->sprite_width; - float center_y_sq = center_y / ptex->sprite_height; - - float width_sq = box.getSize().x / (ptex->sprite_width * zoom); - float height_sq = box.getSize().y / (ptex->sprite_height * zoom); - float left_edge = center_x_sq - (width_sq / 2.0); - float top_edge = center_y_sq - (height_sq / 2.0); - - int left_spritepixels = center_x - (box.getSize().x / 2.0 / zoom); - int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom); - - //sprite.setScale(sf::Vector2f(zoom, zoom)); - sf::RectangleShape r; // for colors and overlays - r.setSize(sf::Vector2f(ptex->sprite_width * zoom, ptex->sprite_height * zoom)); - r.setOutlineThickness(0); - - int x_limit = left_edge + width_sq + 2; - if (x_limit > grid_x) x_limit = grid_x; - - int y_limit = top_edge + height_sq + 2; - if (y_limit > grid_y) y_limit = grid_y; - - // base layer - bottom color, tile sprite ("ground") - for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0); - x < x_limit; //x < view_width; - x+=1) - { - //for (float y = (top_edge >= 0 ? top_edge : 0); - for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0); - y < y_limit; //y < view_height; - y+=1) - { - auto pixel_pos = sf::Vector2f( - (x*ptex->sprite_width - left_spritepixels) * zoom, - (y*ptex->sprite_height - top_spritepixels) * zoom ); - - auto gridpoint = at(std::floor(x), std::floor(y)); - - //sprite.setPosition(pixel_pos); - - r.setPosition(pixel_pos); - r.setFillColor(gridpoint.color); - renderTexture.draw(r); - - // tilesprite - // if discovered but not visible, set opacity to 90% - // if not discovered... just don't draw it? - if (gridpoint.tilesprite != -1) { - sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);; - renderTexture.draw(sprite); - } - } - } - - // middle layer - entities - // disabling entity rendering until I can render their UISprite inside the rendertexture (not directly to window) - for (auto e : *entities) { - // TODO skip out-of-bounds entities (grid square not visible at all, check for partially on visible grid squares / floating point grid position) - //auto drawent = e->cGrid->indexsprite.drawable(); - auto& drawent = e->sprite; - //drawent.setScale(zoom, zoom); - drawent.setScale(sf::Vector2f(zoom, zoom)); - auto pixel_pos = sf::Vector2f( - (e->position.x*ptex->sprite_width - left_spritepixels) * zoom, - (e->position.y*ptex->sprite_height - top_spritepixels) * zoom ); - //drawent.setPosition(pixel_pos); - //renderTexture.draw(drawent); - drawent.render(pixel_pos, renderTexture); - } - - - // top layer - opacity for discovered / visible status (debug, basically) - /* // Disabled until I attach a "perspective" - for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0); - x < x_limit; //x < view_width; - x+=1) - { - //for (float y = (top_edge >= 0 ? top_edge : 0); - for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0); - y < y_limit; //y < view_height; - y+=1) - { - - auto pixel_pos = sf::Vector2f( - (x*itex->grid_size - left_spritepixels) * zoom, - (y*itex->grid_size - top_spritepixels) * zoom ); - - auto gridpoint = at(std::floor(x), std::floor(y)); - - sprite.setPosition(pixel_pos); - - r.setPosition(pixel_pos); - - // visible & discovered layers for testing purposes - if (!gridpoint.discovered) { - r.setFillColor(sf::Color(16, 16, 20, 192)); // 255 opacity for actual blackout - renderTexture.draw(r); - } else if (!gridpoint.visible) { - r.setFillColor(sf::Color(32, 32, 40, 128)); - renderTexture.draw(r); - } - - // overlay - - // uisprite - } - } - */ - - // grid lines for testing & validation - /* - sf::Vertex line[] = - { - sf::Vertex(sf::Vector2f(0, 0), sf::Color::Red), - sf::Vertex(box.getSize(), sf::Color::Red), - - }; - - renderTexture.draw(line, 2, sf::Lines); - sf::Vertex lineb[] = - { - sf::Vertex(sf::Vector2f(0, box.getSize().y), sf::Color::Blue), - sf::Vertex(sf::Vector2f(box.getSize().x, 0), sf::Color::Blue), - - }; - - renderTexture.draw(lineb, 2, sf::Lines); - */ - - // render to window - renderTexture.display(); - Resources::game->getWindow().draw(output); - -} - -UIGridPoint& UIGrid::at(int x, int y) -{ - return points[y * grid_x + x]; -} - -PyObjectsEnum UIGrid::derived_type() -{ - return PyObjectsEnum::UIGRID; -} - -std::shared_ptr UIGrid::getTexture() -{ - return ptex; -} diff --git a/src/UI.h b/src/UI.h index c845baf..61e4576 100644 --- a/src/UI.h +++ b/src/UI.h @@ -6,2509 +6,18 @@ #include "Resources.h" #include -#define ui_h - #include "PyCallable.h" #include "PyTexture.h" #include "PyColor.h" -//#include "PyLinkedColor.h" #include "PyVector.h" #include "PyFont.h" -enum PyObjectsEnum : int -{ - UIFRAME = 1, - UICAPTION, - UISPRITE, - UIGRID -}; - -class UIDrawable -{ -public: - //UIDrawable* parent; - void render(); - virtual void render(sf::Vector2f) = 0; - //virtual sf::Rect aabb(); // not sure I care about this yet - //virtual sf::Vector2i position(); - //bool handle_event(/* ??? click, scroll, keystroke*/); - //std::string action; - virtual PyObjectsEnum derived_type() = 0; - - // Mouse input handling - callable object, methods to find event's destination - //PyObject* click_callable; - std::unique_ptr click_callable; - virtual UIDrawable* click_at(sf::Vector2f point) = 0; - void click_register(PyObject*); - void click_unregister(); - - UIDrawable(); -}; - -//Python object types & forward declarations -/* -typedef struct { - PyObject_HEAD - sf::Color color; -} PyColorObject; -*/ - -/* // Moved to PyColor.h -typedef struct { - PyObject_HEAD - std::shared_ptr data; -} PyColorObject; -*/ - -class UIFrame: public UIDrawable -{ -public: - UIFrame(float, float, float, float); - UIFrame(); - ~UIFrame(); - sf::RectangleShape box; - // todone: why does UIFrame have x,y,w,h AND a box? Which one should be used for bounds checks? (floats removed) - - //Simulate RectangleShape - //float x, y, w, h, - float outline; - std::shared_ptr>> children; - void render(sf::Vector2f) override final; - void move(sf::Vector2f); - - PyObjectsEnum derived_type() override final; // { return PyObjectsEnum::UIFrame; }; - virtual UIDrawable* click_at(sf::Vector2f point) override final; - /* - sf::Color fillColor(); // getter - void fillColor(sf::Color c); // C++ setter - void fillColor(PyColorObject* pyColor); // Python setter - - sf::Color outlineColor(); // getter - void outlineColor(sf::Color c); // C++ setter - void outlineColor(PyColorObject* pyColor); // Python setter - */ - -private: - //std::shared_ptr fillColor, outlineColor; - /* - sf::Color *_fillColor, *_outlineColor; - PyColorObject *pyFillColor, *pyOutlineColor; - */ -}; - -class UICaption: public UIDrawable -{ -public: - sf::Text text; - void render(sf::Vector2f) override final; - PyObjectsEnum derived_type() override final; // { return PyObjectsEnum::UICaption; }; - virtual UIDrawable* click_at(sf::Vector2f point) override final; -}; - -class UISprite: public UIDrawable -{ -private: - int sprite_index; - sf::Sprite sprite; -protected: - std::shared_ptr ptex; -public: - UISprite(); - //UISprite(IndexTexture*, int, float, float, float); - //UISprite(IndexTexture*, int, sf::Vector2f, float); - UISprite(std::shared_ptr, int, sf::Vector2f, float); - void update(); - void render(sf::Vector2f) override final; - virtual UIDrawable* click_at(sf::Vector2f point) override final; - - // 7DRL hack - TODO apply RenderTexture concept to all UIDrawables (via `sf::RenderTarget`) - void render(sf::Vector2f, sf::RenderTexture&); - //IndexTexture* itex; - //sf::Vector2f pos; - //float scale; - //void setPosition(float, float); - void setPosition(sf::Vector2f); - sf::Vector2f getPosition(); - void setScale(sf::Vector2f); - sf::Vector2f getScale(); - void setSpriteIndex(int); - int getSpriteIndex(); - - void setTexture(std::shared_ptr _ptex, int _sprite_index=-1); - std::shared_ptr getTexture(); - - PyObjectsEnum derived_type() override final; // { return PyObjectsEnum::UISprite; }; -}; - -// UIGridPoint - revised grid data for each point -class UIGridPoint -{ -public: - sf::Color color, color_overlay; - bool walkable, transparent; - int tilesprite, tile_overlay, uisprite; - UIGridPoint(); -}; - -// UIGridPointState - entity-specific info for each cell -class UIGridPointState -{ -public: - bool visible, discovered; -}; - -class UIGrid; - -// TODO: make UIEntity a drawable(?) Maybe just rely on UISprite/UIGrid to -// somehow properly render the thing? Poorly designed interface -class UIEntity//: public UIDrawable -{ -public: - //PyObject* self; - std::shared_ptr grid; - std::vector gridstate; - UISprite sprite; - sf::Vector2f position; //(x,y) in grid coordinates; float for animation - void render(sf::Vector2f); //override final; - - UIEntity(); - UIEntity(UIGrid&); - -}; - -class UIGrid: public UIDrawable -{ -private: - std::shared_ptr ptex; -public: - UIGrid(); - //UIGrid(int, int, IndexTexture*, float, float, float, float); - UIGrid(int, int, std::shared_ptr, sf::Vector2f, sf::Vector2f); - void update(); - void render(sf::Vector2f) override final; - UIGridPoint& at(int, int); - PyObjectsEnum derived_type() override final; - //void setSprite(int); - virtual UIDrawable* click_at(sf::Vector2f point) override final; - - int grid_x, grid_y; - //int grid_size; // grid sizes are implied by IndexTexture now - sf::RectangleShape box; - float center_x, center_y, zoom; - //IndexTexture* itex; - std::shared_ptr getTexture(); - sf::Sprite sprite, output; - sf::RenderTexture renderTexture; - std::vector points; - std::shared_ptr>> entities; -}; - -/* -template -struct CPythonSharedObject { - PyObject_HEAD - std::shared_ptr data; -}; - -typedef CPythonSharedObject PyUIFrameObject; -*/ - -typedef struct { - PyObject_HEAD - std::shared_ptr data; -} PyUIFrameObject; - -typedef struct { - PyObject_HEAD - std::shared_ptr data; - PyObject* font; -} PyUICaptionObject; - -typedef struct { - PyObject_HEAD - std::shared_ptr data; - //PyObject* texture; -} PyUISpriteObject; - -typedef struct { - PyObject_HEAD - std::shared_ptr>> data; -} PyUICollectionObject; - -typedef struct { - PyObject_HEAD - UIGridPoint* data; - std::shared_ptr grid; -} PyUIGridPointObject; - -typedef struct { - PyObject_HEAD - UIGridPointState* data; - std::shared_ptr grid; - std::shared_ptr entity; -} PyUIGridPointStateObject; - -typedef struct { - PyObject_HEAD - std::shared_ptr data; - //PyObject* texture; -} PyUIEntityObject; - -typedef struct { - PyObject_HEAD - std::shared_ptr data; - //PyObject* texture; -} PyUIGridObject; - -namespace mcrfpydef { - //PyObject* py_instance(std::shared_ptr source); - // This function segfaults on tp_alloc for an unknown reason, but works inline with mcrfpydef:: methods. - -#define RET_PY_INSTANCE(target) { \ -switch (target->derived_type()) \ -{ \ - case PyObjectsEnum::UIFRAME: \ - { \ - PyUIFrameObject* o = (PyUIFrameObject*)((&PyUIFrameType)->tp_alloc(&PyUIFrameType, 0)); \ - if (o) \ - { \ - auto p = std::static_pointer_cast(target); \ - o->data = p; \ - auto utarget = o->data; \ - } \ - return (PyObject*)o; \ - } \ - case PyObjectsEnum::UICAPTION: \ - { \ - PyUICaptionObject* o = (PyUICaptionObject*)((&PyUICaptionType)->tp_alloc(&PyUICaptionType, 0)); \ - if (o) \ - { \ - auto p = std::static_pointer_cast(target); \ - o->data = p; \ - auto utarget = o->data; \ - } \ - return (PyObject*)o; \ - } \ - case PyObjectsEnum::UISPRITE: \ - { \ - PyUISpriteObject* o = (PyUISpriteObject*)((&PyUISpriteType)->tp_alloc(&PyUISpriteType, 0)); \ - if (o) \ - { \ - auto p = std::static_pointer_cast(target); \ - o->data = p; \ - auto utarget = o->data; \ - } \ - return (PyObject*)o; \ - } \ - case PyObjectsEnum::UIGRID: \ - { \ - PyUIGridObject* o = (PyUIGridObject*)((&PyUIGridType)->tp_alloc(&PyUIGridType, 0)); \ - if (o) \ - { \ - auto p = std::static_pointer_cast(target); \ - o->data = p; \ - auto utarget = o->data; \ - } \ - return (PyObject*)o; \ - } \ -} \ -} -// end macro definition - - // Color Definitions - // struct, members, new, set_member, PyTypeObject - - /* for reference: the structs to implement - typedef struct { - PyObject_HEAD - std::shared_ptr data; - } PyColorObject; - - typedef struct { - PyObject_HEAD - std::shared_ptr data; - } PyUIFrameObject; - - typedef struct { - PyObject_HEAD - std::shared_ptr data; - } PyUICaptionObject; - - typedef struct { - PyObject_HEAD - std::shared_ptr data; - } PyUISpriteObject; - */ - -// -// Clickable / Callable Object Assignment -// -static PyObject* PyUIDrawable_get_click(PyUIGridObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum - PyObject* ptr; - - switch (objtype) - { - case PyObjectsEnum::UIFRAME: - ptr = ((PyUIFrameObject*)self)->data->click_callable->borrow(); - break; - case PyObjectsEnum::UICAPTION: - ptr = ((PyUICaptionObject*)self)->data->click_callable->borrow(); - break; - case PyObjectsEnum::UISPRITE: - ptr = ((PyUISpriteObject*)self)->data->click_callable->borrow(); - break; - case PyObjectsEnum::UIGRID: - ptr = ((PyUIGridObject*)self)->data->click_callable->borrow(); - break; - default: - PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _get_click"); - return NULL; - } - if (ptr && ptr != Py_None) - return ptr; - else - return Py_None; -} - -static int PyUIDrawable_set_click(PyUIGridObject* self, PyObject* value, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum - UIDrawable* target; - switch (objtype) - { - case PyObjectsEnum::UIFRAME: - target = (((PyUIFrameObject*)self)->data.get()); - break; - case PyObjectsEnum::UICAPTION: - target = (((PyUICaptionObject*)self)->data.get()); - break; - case PyObjectsEnum::UISPRITE: - target = (((PyUISpriteObject*)self)->data.get()); - break; - case PyObjectsEnum::UIGRID: - target = (((PyUIGridObject*)self)->data.get()); - break; - default: - PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _set_click"); - return -1; - } - - if (value == Py_None) - { - target->click_unregister(); - } else { - target->click_register(value); - } - return 0; -} - -// End Clickability implementation - - - /* - * - * Begin PyFontType defs - * - */ - /* - typedef struct { - PyObject_HEAD - std::shared_ptr data; - } PyFontObject; - - static int PyFont_init(PyFontObject* self, PyObject* args, PyObject* kwds) - { - //std::cout << "Init called\n"; - static const char* keywords[] = { "filename", nullptr }; - char* filename; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", const_cast(keywords), &filename)) - { - return -1; - } - self->data->loadFromFile((std::string)filename); - return 0; - } - - static PyTypeObject PyFontType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.Font", - .tp_basicsize = sizeof(PyFontObject), - .tp_itemsize = 0, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("SFML Font Object"), - .tp_init = (initproc)PyFont_init, - .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* - { - PyFontObject* self = (PyFontObject*)type->tp_alloc(type, 0); - self->data = std::make_shared(); - return (PyObject*)self; - } - }; - */ - - /* - * - * End PyFontType defs - * - */ - - -// static PyObject* PyColor_get_member(PyColorObject* self, void* closure) -// { -// auto member_ptr = reinterpret_cast(closure); -// if (member_ptr == 0) -// return PyLong_FromLong(self->data->r); -// else if (member_ptr == 1) -// return PyLong_FromLong(self->data->g); -// else if (member_ptr == 2) -// return PyLong_FromLong(self->data->b); -// else if (member_ptr == 3) -// return PyLong_FromLong(self->data->a); -// else -// { -// PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); -// return nullptr; -// } -// } -// -// static int PyColor_set_member(PyColorObject* self, PyObject* value, void* closure) -// { -// if (PyLong_Check(value)) -// { -// long int_val = PyLong_AsLong(value); -// if (int_val < 0) -// int_val = 0; -// else if (int_val > 255) -// int_val = 255; -// auto member_ptr = reinterpret_cast(closure); -// if (member_ptr == 0) -// self->data->r = static_cast(int_val); -// else if (member_ptr == 1) -// self->data->g = static_cast(int_val); -// else if (member_ptr == 2) -// self->data->b = static_cast(int_val); -// else if (member_ptr == 3) -// self->data->a = static_cast(int_val); -// } -// else -// { -// PyErr_SetString(PyExc_TypeError, "Value must be an integer."); -// return -1; -// } -// return 0; -// } -// -// static PyGetSetDef PyColor_getsetters[] = { -// {"r", (getter)PyColor_get_member, (setter)PyColor_set_member, "Red component", (void*)0}, -// {"g", (getter)PyColor_get_member, (setter)PyColor_set_member, "Green component", (void*)1}, -// {"b", (getter)PyColor_get_member, (setter)PyColor_set_member, "Blue component", (void*)2}, -// {"a", (getter)PyColor_get_member, (setter)PyColor_set_member, "Alpha component", (void*)3}, -// {NULL} -// }; -// -// -// static PyTypeObject PyColorType = { -// //PyVarObject_HEAD_INIT(NULL, 0) -// .tp_name = "mcrfpy.Color", -// .tp_basicsize = sizeof(PyColorObject), -// .tp_itemsize = 0, -// .tp_dealloc = (destructor)[](PyObject* self) -// { -// PyColorObject* obj = (PyColorObject*)self; -// obj->data.reset(); -// Py_TYPE(self)->tp_free(self); -// }, -// //.tp_repr = (reprfunc)PyUIFrame_repr, -// //.tp_hash = NULL, -// //.tp_iter -// //.tp_iternext -// .tp_flags = Py_TPFLAGS_DEFAULT, -// .tp_doc = PyDoc_STR("SFML Color object (RGBA)"), -// //.tp_methods = PyUIFrame_methods, -// //.tp_members = PyColor_members, -// .tp_getset = PyColor_getsetters, -// //.tp_base = NULL, -// //.tp_init = (initproc)PyUIFrame_init, -// .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* -// { -// PyColorObject* self = (PyColorObject*)type->tp_alloc(type, 0); -// if (self) self->data = std::make_shared(); -// return (PyObject*)self; -// } -// }; - - /* - * - * Begin template generation for PyUICaptionType - * - */ - - static PyObject* PyUICaption_get_float_member(PyUICaptionObject* self, void* closure) - { - auto member_ptr = reinterpret_cast(closure); - if (member_ptr == 0) - return PyFloat_FromDouble(self->data->text.getPosition().x); - else if (member_ptr == 1) - return PyFloat_FromDouble(self->data->text.getPosition().y); - else if (member_ptr == 4) - return PyFloat_FromDouble(self->data->text.getOutlineThickness()); - else if (member_ptr == 5) - return PyLong_FromLong(self->data->text.getCharacterSize()); - else - { - PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); - return nullptr; - } - } - - static int PyUICaption_set_float_member(PyUICaptionObject* self, PyObject* value, void* closure) - { - float val; - auto member_ptr = reinterpret_cast(closure); - if (PyFloat_Check(value)) - { - val = PyFloat_AsDouble(value); - } - else if (PyLong_Check(value)) - { - val = PyLong_AsLong(value); - } - else - { - PyErr_SetString(PyExc_TypeError, "Value must be an integer."); - return -1; - } - if (member_ptr == 0) //x - self->data->text.setPosition(val, self->data->text.getPosition().y); - else if (member_ptr == 1) //y - self->data->text.setPosition(self->data->text.getPosition().x, val); - else if (member_ptr == 4) //outline - self->data->text.setOutlineThickness(val); - else if (member_ptr == 5) // character size - self->data->text.setCharacterSize(val); - return 0; - } - - static PyObject* PyUICaption_get_vec_member(PyUICaptionObject* self, void* closure) - { - return PyVector(self->data->text.getPosition()).pyObject(); - } - - static int PyUICaption_set_vec_member(PyUICaptionObject* self, PyObject* value, void* closure) - { - self->data->text.setPosition(PyVector::fromPy(value)); - return 0; - } - - static PyObject* PyUICaption_get_color_member(PyUICaptionObject* self, void* closure) - { - // validate closure (should be impossible to be wrong, but it's thorough) - auto member_ptr = reinterpret_cast(closure); - if (member_ptr != 0 && member_ptr != 1) - { - PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); - return nullptr; - } - - // TODO: manually calling tp_alloc to create a PyColorObject seems like an antipattern - /* - PyTypeObject* colorType = &PyLinkedColorType; - PyObject* pyColor = colorType->tp_alloc(colorLinkedType, 0); - if (pyColor == NULL) - { - std::cout << "failure to allocate mcrfpy.LinkedColor / PyLinkedColorType" << std::endl; - return NULL; - } - PyColorObject* pyColorObj = reinterpret_cast(pyColor); - */ - - // fetch correct member data - sf::Color color; - //sf::Color (*cgetter)(); - //void (*csetter)(sf::Color); - //std::function csetter; - //std::function cgetter; - if (member_ptr == 0) - { - color = self->data->text.getFillColor(); - //return Py_BuildValue("(iii)", color.r, color.g, color.b); - //csetter = &self->data->text.setFillColor; - //cgetter = &self->data->text.getFillColor; - //csetter = [s = self->data](sf::Color c){s->text.setFillColor(c);}; - //cgetter = [s = self->data](){return s->text.getFillColor();}; - } - else if (member_ptr == 1) - { - color = self->data->text.getOutlineColor(); - //return Py_BuildValue("(iii)", color.r, color.g, color.b); - //csetter = &self->data->text.setOutlineColor; - //cgetter = &self->data->text.getOutlineColor; - //csetter = [s = self->data](sf::Color c){s->text.setOutlineColor(c);}; - //cgetter = [s = self->data](){return s->text.getOutlineColor();}; - } - - // initialize new mcrfpy.Color instance - //pyColorObj->data = std::make_shared(color); - //PyLinkedColor::fromPy(pyColorObj).set(color); - //auto linkedcolor = PyLinkedColor(csetter, cgetter, self->data, member_ptr); - //linkedcolor.set(color); // don't need to set a linked color! - - //return pyColor; - return PyColor(color).pyObject(); - } - - static int PyUICaption_set_color_member(PyUICaptionObject* self, PyObject* value, void* closure) - { - auto member_ptr = reinterpret_cast(closure); - //TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse - int r, g, b, a; - if (PyObject_IsInstance(value, (PyObject*)&PyColorType)) - { - // get value from mcrfpy.Color instance - /* - PyColorObject* color = reinterpret_cast(value); - r = color->data->r; - g = color->data->g; - b = color->data->b; - a = color->data->a; - */ - //std::cout << "Build LinkedColor" << std::endl; - //auto lc = PyLinkedColor::fromPy(value); - auto c = ((PyColorObject*)value)->data; - //std::cout << "Fetch value" << std::endl; - //auto c = lc.get(); - r = c.r; g = c.g; b = c.b; a = c.a; - std::cout << "got " << int(r) << ", " << int(g) << ", " << int(b) << ", " << int(a) << std::endl; - } - else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4) - { - // reject non-Color, non-tuple value - PyErr_SetString(PyExc_TypeError, "Value must be a tuple of 3 or 4 integers or an mcrfpy.Color object."); - return -1; - } - else // get value from tuples - { - r = PyLong_AsLong(PyTuple_GetItem(value, 0)); - g = PyLong_AsLong(PyTuple_GetItem(value, 1)); - b = PyLong_AsLong(PyTuple_GetItem(value, 2)); - a = 255; - - if (PyTuple_Size(value) == 4) - { - a = PyLong_AsLong(PyTuple_GetItem(value, 3)); - } - } - - if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255) - { - PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255."); - return -1; - } - - if (member_ptr == 0) - { - self->data->text.setFillColor(sf::Color(r, g, b, a)); - } - else if (member_ptr == 1) - { - self->data->text.setOutlineColor(sf::Color(r, g, b, a)); - } - else - { - PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); - return -1; - } - - return 0; - } - - static PyObject* PyUICaption_get_text(PyUICaptionObject* self, void* closure) - { - Resources::caption_buffer = self->data->text.getString(); - return PyUnicode_FromString(Resources::caption_buffer.c_str()); - } - - static int PyUICaption_set_text(PyUICaptionObject* self, PyObject* value, void* closure) - { - PyObject* s = PyObject_Str(value); - PyObject * temp_bytes = PyUnicode_AsEncodedString(s, "UTF-8", "strict"); // Owned reference - if (temp_bytes != NULL) { - Resources::caption_buffer = PyBytes_AS_STRING(temp_bytes); // Borrowed pointer - Py_DECREF(temp_bytes); - } - self->data->text.setString(Resources::caption_buffer); - return 0; - } - - static PyGetSetDef PyUICaption_getsetters[] = { - {"x", (getter)PyUICaption_get_float_member, (setter)PyUICaption_set_float_member, "X coordinate of top-left corner", (void*)0}, - {"y", (getter)PyUICaption_get_float_member, (setter)PyUICaption_set_float_member, "Y coordinate of top-left corner", (void*)1}, - {"pos", (getter)PyUICaption_get_vec_member, (setter)PyUICaption_set_vec_member, "(x, y) vector", (void*)0}, - //{"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2}, - //{"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3}, - {"outline", (getter)PyUICaption_get_float_member, (setter)PyUICaption_set_float_member, "Thickness of the border", (void*)4}, - {"fill_color", (getter)PyUICaption_get_color_member, (setter)PyUICaption_set_color_member, "Fill color of the text", (void*)0}, - {"outline_color", (getter)PyUICaption_get_color_member, (setter)PyUICaption_set_color_member, "Outline color of the text", (void*)1}, - //{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL}, - {"text", (getter)PyUICaption_get_text, (setter)PyUICaption_set_text, "The text displayed", NULL}, - {"size", (getter)PyUICaption_get_float_member, (setter)PyUICaption_set_float_member, "Text size (integer) in points", (void*)5}, - {"click", (getter)PyUIDrawable_get_click, (setter)PyUIDrawable_set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UICAPTION}, - {NULL} - }; - - static PyObject* PyUICaption_repr(PyUICaptionObject* self) - { - std::ostringstream ss; - if (!self->data) ss << ""; - else { - auto text = self->data->text; - auto fc = text.getFillColor(); - auto oc = text.getOutlineColor(); - ss << ""; - } - std::string repr_str = ss.str(); - return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); - } - - static int PyUICaption_init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) - { - //std::cout << "Init called\n"; - static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", nullptr }; - float x = 0.0f, y = 0.0f; - char* text; - PyObject* font, fill_color, outline_color; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOO", - const_cast(keywords), &x, &y, &text, &font, &fill_color, &outline_color)) - { - return -1; - } - - // check types for font, fill_color, outline_color - // - // Set Font - // - std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl; - if (font != NULL && !PyObject_IsInstance(font, (PyObject*)&PyFontType)){ - PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance"); - return -1; - } else if (font != NULL) - { - auto font_obj = (PyFontObject*)font; - self->data->text.setFont(font_obj->data->font); - self->font = font; - Py_INCREF(font); - } else - { - // default font - //self->data->text.setFont(Resources::game->getFont()); - } - - // - // Set Color - // - self->data->text.setPosition(sf::Vector2f(x, y)); - self->data->text.setString((std::string)text); - self->data->text.setFillColor(sf::Color(0,0,0,255)); - self->data->text.setOutlineColor(sf::Color(128,128,128,255)); - - return 0; - } - - static PyTypeObject PyUICaptionType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.Caption", - .tp_basicsize = sizeof(PyUICaptionObject), - .tp_itemsize = 0, - .tp_dealloc = (destructor)[](PyObject* self) - { - PyUICaptionObject* obj = (PyUICaptionObject*)self; - // release reference to font object - if (obj->font) Py_DECREF(obj->font); - obj->data.reset(); - Py_TYPE(self)->tp_free(self); - }, - .tp_repr = (reprfunc)PyUICaption_repr, - //.tp_hash = NULL, - //.tp_iter - //.tp_iternext - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("docstring"), - //.tp_methods = PyUIFrame_methods, - //.tp_members = PyUIFrame_members, - .tp_getset = PyUICaption_getsetters, - //.tp_base = NULL, - .tp_init = (initproc)PyUICaption_init, - .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* - { - PyUICaptionObject* self = (PyUICaptionObject*)type->tp_alloc(type, 0); - if (self) self->data = std::make_shared(); - return (PyObject*)self; - } - }; - - /* - * - * End PyUICaptionType generation - * - */ - - - /* - * - * Begin template generation for PyUIFrameType - * - */ - - static PyObject* PyUIFrame_get_float_member(PyUIFrameObject* self, void* closure) - { - auto member_ptr = reinterpret_cast(closure); - if (member_ptr == 0) - return PyFloat_FromDouble(self->data->box.getPosition().x); - else if (member_ptr == 1) - return PyFloat_FromDouble(self->data->box.getPosition().y); - else if (member_ptr == 2) - return PyFloat_FromDouble(self->data->box.getSize().x); - else if (member_ptr == 3) - return PyFloat_FromDouble(self->data->box.getSize().y); - else if (member_ptr == 4) - return PyFloat_FromDouble(self->data->box.getOutlineThickness()); - else - { - PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); - return nullptr; - } - } - - static int PyUIFrame_set_float_member(PyUIFrameObject* self, PyObject* value, void* closure) - { - float val; - auto member_ptr = reinterpret_cast(closure); - if (PyFloat_Check(value)) - { - val = PyFloat_AsDouble(value); - } - else if (PyLong_Check(value)) - { - val = PyLong_AsLong(value); - } - else - { - PyErr_SetString(PyExc_TypeError, "Value must be an integer."); - return -1; - } - if (member_ptr == 0) //x - self->data->box.setPosition(val, self->data->box.getPosition().y); - else if (member_ptr == 1) //y - self->data->box.setPosition(self->data->box.getPosition().x, val); - else if (member_ptr == 2) //w - self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y)); - else if (member_ptr == 3) //h - self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val)); - else if (member_ptr == 4) //outline - self->data->box.setOutlineThickness(val); - return 0; - } - - static PyObject* PyUIFrame_get_color_member(PyUIFrameObject* self, void* closure) - { - // validate closure (should be impossible to be wrong, but it's thorough) - auto member_ptr = reinterpret_cast(closure); - if (member_ptr != 0 && member_ptr != 1) - { - PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); - return nullptr; - } - PyTypeObject* colorType = &PyColorType; - PyObject* pyColor = colorType->tp_alloc(colorType, 0); - if (pyColor == NULL) - { - std::cout << "failure to allocate mcrfpy.Color / PyColorType" << std::endl; - return NULL; - } - PyColorObject* pyColorObj = reinterpret_cast(pyColor); - - // fetch correct member data - sf::Color color; - if (member_ptr == 0) - { - color = self->data->box.getFillColor(); - //return Py_BuildValue("(iii)", color.r, color.g, color.b); - } - else if (member_ptr == 1) - { - color = self->data->box.getOutlineColor(); - //return Py_BuildValue("(iii)", color.r, color.g, color.b); - } - - // initialize new mcrfpy.Color instance - //pyColorObj->data = std::make_shared(color); - //PyColor::fromPy(pyColorObj).set(color); - - // TODO - supposed to return Linked - return PyColor(color).pyObject(); - } - - static int PyUIFrame_set_color_member(PyUIFrameObject* self, PyObject* value, void* closure) - { - //TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse - auto member_ptr = reinterpret_cast(closure); - int r, g, b, a; - if (PyObject_IsInstance(value, (PyObject*)&PyColorType)) - { - // get value from mcrfpy.Color instance - /* - PyColorObject* color = reinterpret_cast(value); - r = color->data->r; - g = color->data->g; - b = color->data->b; - a = color->data->a; - std::cout << "using color: " << r << " " << g << " " << b << " " << a << std::endl; - */ - sf::Color c = ((PyColorObject*)value)->data; - r = c.r; g = c.g; b = c.b; a = c.a; - } - else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4) - { - // reject non-Color, non-tuple value - PyErr_SetString(PyExc_TypeError, "Value must be a tuple of 3 or 4 integers or an mcrfpy.Color object."); - return -1; - } - else // get value from tuples - { - r = PyLong_AsLong(PyTuple_GetItem(value, 0)); - g = PyLong_AsLong(PyTuple_GetItem(value, 1)); - b = PyLong_AsLong(PyTuple_GetItem(value, 2)); - a = 255; - - if (PyTuple_Size(value) == 4) - { - a = PyLong_AsLong(PyTuple_GetItem(value, 3)); - } - } - - if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255) - { - PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255."); - return -1; - } - - if (member_ptr == 0) - { - self->data->box.setFillColor(sf::Color(r, g, b, a)); - } - else if (member_ptr == 1) - { - self->data->box.setOutlineColor(sf::Color(r, g, b, a)); - } - else - { - PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); - return -1; - } - - return 0; - } - - static PyObject* PyUIFrame_get_children(PyUIFrameObject*, void*); - // implementation after the PyUICollectionType definition - - static PyGetSetDef PyUIFrame_getsetters[] = { - {"x", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "X coordinate of top-left corner", (void*)0}, - {"y", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "Y coordinate of top-left corner", (void*)1}, - {"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2}, - {"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3}, - {"outline", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "Thickness of the border", (void*)4}, - {"fill_color", (getter)PyUIFrame_get_color_member, (setter)PyUIFrame_set_color_member, "Fill color of the rectangle", (void*)0}, - {"outline_color", (getter)PyUIFrame_get_color_member, (setter)PyUIFrame_set_color_member, "Outline color of the rectangle", (void*)1}, - {"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL}, - {"click", (getter)PyUIDrawable_get_click, (setter)PyUIDrawable_set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME}, - {NULL} - }; - - static PyObject* PyUIFrame_repr(PyUIFrameObject* self) - { - std::ostringstream ss; - if (!self->data) ss << ""; - else { - auto box = self->data->box; - auto fc = box.getFillColor(); - auto oc = box.getOutlineColor(); - ss << "data->children->size() << " child objects" << - ")>"; - } - std::string repr_str = ss.str(); - return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); - } - - static int PyUIFrame_init(PyUIFrameObject* self, PyObject* args, PyObject* kwds) - { - //std::cout << "Init called\n"; - static const char* keywords[] = { "x", "y", "w", "h", "fill_color", "outline_color", "outline", nullptr }; - float x = 0.0f, y = 0.0f, w = 0.0f, h=0.0f, outline=0.0f; - PyObject* fill_color = 0; - PyObject* outline_color = 0; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffff|OOf", const_cast(keywords), &x, &y, &w, &h, &fill_color, &outline_color, &outline)) - { - return -1; - } - - //self->data->x = x; - //self->data->y = y; - self->data->box.setPosition(sf::Vector2f(x, y)); - self->data->box.setSize(sf::Vector2f(w, h)); - self->data->box.setOutlineThickness(outline); - // getsetter abuse because I haven't standardized Color object parsing (TODO) - int err_val = 0; - if (fill_color && fill_color != Py_None) err_val = PyUIFrame_set_color_member(self, fill_color, (void*)0); - else self->data->box.setFillColor(sf::Color(0,0,0,255)); - if (err_val) return err_val; - if (outline_color && outline_color != Py_None) err_val = PyUIFrame_set_color_member(self, outline_color, (void*)1); - else self->data->box.setOutlineColor(sf::Color(128,128,128,255)); - if (err_val) return err_val; - return 0; - } - - static PyTypeObject PyUIFrameType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.Frame", - .tp_basicsize = sizeof(PyUIFrameObject), - .tp_itemsize = 0, - .tp_dealloc = (destructor)[](PyObject* self) - { - PyUIFrameObject* obj = (PyUIFrameObject*)self; - obj->data.reset(); - Py_TYPE(self)->tp_free(self); - }, - .tp_repr = (reprfunc)PyUIFrame_repr, - //.tp_hash = NULL, - //.tp_iter - //.tp_iternext - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("docstring"), - //.tp_methods = PyUIFrame_methods, - //.tp_members = PyUIFrame_members, - .tp_getset = PyUIFrame_getsetters, - //.tp_base = NULL, - .tp_init = (initproc)PyUIFrame_init, - .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* - { - PyUIFrameObject* self = (PyUIFrameObject*)type->tp_alloc(type, 0); - if (self) self->data = std::make_shared(); - return (PyObject*)self; - } - }; - - /* - * - * End auto-generated PyUIFrameType generation - * - */ - - /* - * - * Begin Python Class Instantiator (iterator helper) - * - */ - - /* // definition can't go in the header file - PyObject* py_instance(UIDrawable* obj) - { - - } - */ - - /* - * - * End Python Class Instantitator (iterator helper) - * - */ - - /* - * - * Begin template generation for PyUISpriteType - * - */ - - static PyObject* PyUISprite_get_float_member(PyUISpriteObject* self, void* closure) - { - auto member_ptr = reinterpret_cast(closure); - if (member_ptr == 0) - return PyFloat_FromDouble(self->data->getPosition().x); - else if (member_ptr == 1) - return PyFloat_FromDouble(self->data->getPosition().y); - else if (member_ptr == 2) - return PyFloat_FromDouble(self->data->getScale().x); // scale X and Y are identical, presently - else - { - PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); - return nullptr; - } - } - - - static int PyUISprite_set_float_member(PyUISpriteObject* self, PyObject* value, void* closure) - { - float val; - auto member_ptr = reinterpret_cast(closure); - if (PyFloat_Check(value)) - { - val = PyFloat_AsDouble(value); - } - else if (PyLong_Check(value)) - { - val = PyLong_AsLong(value); - } - else - { - PyErr_SetString(PyExc_TypeError, "Value must be a floating point number."); - return -1; - } - if (member_ptr == 0) //x - self->data->setPosition(sf::Vector2f(val, self->data->getPosition().y)); - else if (member_ptr == 1) //y - self->data->setPosition(sf::Vector2f(self->data->getPosition().x, val)); - else if (member_ptr == 2) // scale - self->data->setScale(sf::Vector2f(val, val)); - return 0; - } - - static PyObject* PyUISprite_get_int_member(PyUISpriteObject* self, void* closure) - { - auto member_ptr = reinterpret_cast(closure); - if (true) {} - else - { - PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); - return nullptr; - } - - return PyLong_FromDouble(self->data->getSpriteIndex()); - } - - - static int PyUISprite_set_int_member(PyUISpriteObject* self, PyObject* value, void* closure) - { - int val; - auto member_ptr = reinterpret_cast(closure); - if (PyLong_Check(value)) - { - val = PyLong_AsLong(value); - } - else - { - PyErr_SetString(PyExc_TypeError, "Value must be an integer."); - return -1; - } - //self->data->sprite_index = val; - //self->data->sprite.setTextureRect(self->data->itex->spriteCoordinates(val)); - self->data->setSpriteIndex(val); - return 0; - } - - static PyObject* PyUISprite_get_texture(PyUISpriteObject* self, void* closure) - { - return self->data->getTexture()->pyObject(); - } - - static int PyUISprite_set_texture(PyUISpriteObject* self, PyObject* value, void* closure) - { - return -1; - } - - static PyGetSetDef PyUISprite_getsetters[] = { - {"x", (getter)PyUISprite_get_float_member, (setter)PyUISprite_set_float_member, "X coordinate of top-left corner", (void*)0}, - {"y", (getter)PyUISprite_get_float_member, (setter)PyUISprite_set_float_member, "Y coordinate of top-left corner", (void*)1}, - {"scale", (getter)PyUISprite_get_float_member, (setter)PyUISprite_set_float_member, "Size factor", (void*)2}, - {"sprite_number", (getter)PyUISprite_get_int_member, (setter)PyUISprite_set_int_member, "Which sprite on the texture is shown", NULL}, - {"texture", (getter)PyUISprite_get_texture, (setter)PyUISprite_set_texture, "Texture object", NULL}, - {"click", (getter)PyUIDrawable_get_click, (setter)PyUIDrawable_set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE}, - {NULL} - }; - - static PyObject* PyUISprite_repr(PyUISpriteObject* self) - { - std::ostringstream ss; - if (!self->data) ss << ""; - else { - //auto sprite = self->data->sprite; - ss << ""; - } - std::string repr_str = ss.str(); - return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); - } - - static int PyUISprite_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; - PyObject* texture; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif", - const_cast(keywords), &x, &y, &texture, &sprite_index, &scale)) - { - return -1; - } - - // check types for texture - // - // Set Texture - // - if (texture != NULL && !PyObject_IsInstance(texture, (PyObject*)&PyTextureType)){ - PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); - return -1; - } /*else if (texture != NULL) // to be removed: UIObjects don't manage texture references - { - self->texture = texture; - Py_INCREF(texture); - } else - { - // default tex? - }*/ - 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; - } - - static PyTypeObject PyUISpriteType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.Sprite", - .tp_basicsize = sizeof(PyUISpriteObject), - .tp_itemsize = 0, - .tp_dealloc = (destructor)[](PyObject* self) - { - PyUISpriteObject* obj = (PyUISpriteObject*)self; - // release reference to font object - //if (obj->texture) Py_DECREF(obj->texture); - obj->data.reset(); - Py_TYPE(self)->tp_free(self); - }, - .tp_repr = (reprfunc)PyUISprite_repr, - //.tp_hash = NULL, - //.tp_iter - //.tp_iternext - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("docstring"), - //.tp_methods = PyUIFrame_methods, - //.tp_members = PyUIFrame_members, - .tp_getset = PyUISprite_getsetters, - //.tp_base = NULL, - .tp_init = (initproc)PyUISprite_init, - .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* - { - PyUISpriteObject* self = (PyUISpriteObject*)type->tp_alloc(type, 0); - //if (self) self->data = std::make_shared(); - return (PyObject*)self; - } - }; - /* - * - * End template for PyUISpriteType - * - */ - - - - - /* - * - * PyUIGridPoint defs - * - */ - -// TODO: question: are sfColor_to_PyObject and PyObject_to_sfColor duplicitive? How does UIFrame get/set colors? - -// Utility function to convert sf::Color to PyObject* -static PyObject* sfColor_to_PyObject(sf::Color color) { - return Py_BuildValue("(iiii)", color.r, color.g, color.b, color.a); -} - -// Utility function to convert PyObject* to sf::Color -static sf::Color PyObject_to_sfColor(PyObject* obj) { - int r, g, b, a = 255; // Default alpha to fully opaque if not specified - if (!PyArg_ParseTuple(obj, "iii|i", &r, &g, &b, &a)) { - return sf::Color(); // Return default color on parse error - } - return sf::Color(r, g, b, a); -} - -static PyObject* PyUIGridPoint_get_color(PyUIGridPointObject* self, void* closure) { - if (reinterpret_cast(closure) == 0) { // color - return sfColor_to_PyObject(self->data->color); - } else { // color_overlay - return sfColor_to_PyObject(self->data->color_overlay); - } -} - -static int PyUIGridPoint_set_color(PyUIGridPointObject* self, PyObject* value, void* closure) { - sf::Color color = PyObject_to_sfColor(value); - if (reinterpret_cast(closure) == 0) { // color - self->data->color = color; - } else { // color_overlay - self->data->color_overlay = color; - } - return 0; -} - -static PyObject* PyUIGridPoint_get_bool_member(PyUIGridPointObject* self, void* closure) { - if (reinterpret_cast(closure) == 0) { // walkable - return PyBool_FromLong(self->data->walkable); - } else { // transparent - return PyBool_FromLong(self->data->transparent); - } -} - -static int PyUIGridPoint_set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure) { - if (value == Py_True) { - if (reinterpret_cast(closure) == 0) { // walkable - self->data->walkable = true; - } else { // transparent - self->data->transparent = true; - } - } else if (value == Py_False) { - if (reinterpret_cast(closure) == 0) { // walkable - self->data->walkable = false; - } else { // transparent - self->data->transparent = false; - } - } else { - PyErr_SetString(PyExc_ValueError, "Expected a boolean value"); - return -1; - } - return 0; -} - -static PyObject* PyUIGridPoint_get_int_member(PyUIGridPointObject* self, void* closure) { - switch(reinterpret_cast(closure)) { - case 0: return PyLong_FromLong(self->data->tilesprite); - case 1: return PyLong_FromLong(self->data->tile_overlay); - case 2: return PyLong_FromLong(self->data->uisprite); - default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return nullptr; - } -} - -static int PyUIGridPoint_set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure) { - long val = PyLong_AsLong(value); - if (PyErr_Occurred()) return -1; - - switch(reinterpret_cast(closure)) { - case 0: self->data->tilesprite = val; break; - case 1: self->data->tile_overlay = val; break; - case 2: self->data->uisprite = val; break; - default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return -1; - } - return 0; -} - -static PyGetSetDef PyUIGridPoint_getsetters[] = { - {"color", (getter)PyUIGridPoint_get_color, (setter)PyUIGridPoint_set_color, "GridPoint color", (void*)0}, - {"color_overlay", (getter)PyUIGridPoint_get_color, (setter)PyUIGridPoint_set_color, "GridPoint color overlay", (void*)1}, - {"walkable", (getter)PyUIGridPoint_get_bool_member, (setter)PyUIGridPoint_set_bool_member, "Is the GridPoint walkable", (void*)0}, - {"transparent", (getter)PyUIGridPoint_get_bool_member, (setter)PyUIGridPoint_set_bool_member, "Is the GridPoint transparent", (void*)1}, - {"tilesprite", (getter)PyUIGridPoint_get_int_member, (setter)PyUIGridPoint_set_int_member, "Tile sprite index", (void*)0}, - {"tile_overlay", (getter)PyUIGridPoint_get_int_member, (setter)PyUIGridPoint_set_int_member, "Tile overlay sprite index", (void*)1}, - {"uisprite", (getter)PyUIGridPoint_get_int_member, (setter)PyUIGridPoint_set_int_member, "UI sprite index", (void*)2}, - {NULL} /* Sentinel */ -}; - -static PyTypeObject PyUIGridPointType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.GridPoint", - .tp_basicsize = sizeof(PyUIGridPointObject), - .tp_itemsize = 0, - // Methods omitted for brevity - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = "UIGridPoint objects", - .tp_getset = PyUIGridPoint_getsetters, - //.tp_init = (initproc)PyUIGridPoint_init, // TODO Define the init function - .tp_new = PyType_GenericNew, -}; - - /* - * - * end PyUIGridPoint defs - * - */ - - - - /* - * - * PyUIGridPointState defs - * - */ - -static PyObject* PyUIGridPointState_get_bool_member(PyUIGridPointStateObject* self, void* closure) { - if (reinterpret_cast(closure) == 0) { // visible - return PyBool_FromLong(self->data->visible); - } else { // discovered - return PyBool_FromLong(self->data->discovered); - } -} - -static int PyUIGridPointState_set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure) { - if (!PyBool_Check(value)) { - PyErr_SetString(PyExc_TypeError, "Value must be a boolean"); - return -1; - } - - int truthValue = PyObject_IsTrue(value); - if (truthValue < 0) { - return -1; // PyObject_IsTrue returns -1 on error - } - - if (reinterpret_cast(closure) == 0) { // visible - self->data->visible = truthValue; - } else { // discovered - self->data->discovered = truthValue; - } - - return 0; -} - -static PyGetSetDef PyUIGridPointState_getsetters[] = { - {"visible", (getter)PyUIGridPointState_get_bool_member, (setter)PyUIGridPointState_set_bool_member, "Is the GridPointState visible", (void*)0}, - {"discovered", (getter)PyUIGridPointState_get_bool_member, (setter)PyUIGridPointState_set_bool_member, "Has the GridPointState been discovered", (void*)1}, - {NULL} /* Sentinel */ -}; - -static PyTypeObject PyUIGridPointStateType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.GridPointState", - .tp_basicsize = sizeof(PyUIGridPointStateObject), - .tp_itemsize = 0, - // Methods omitted for brevity - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = "UIGridPointState objects", // TODO: Add PyUIGridPointState tp_init - .tp_getset = PyUIGridPointState_getsetters, - .tp_new = PyType_GenericNew, -}; - - - /* - * - * end PyUIGridPointState defs - * - */ - - /* - * - * PyUIEntity defs - * - */ - -// TODO: sf::Vector2f convenience functions here might benefit from a PyVectorObject much like PyColorObject -// Utility function to convert sf::Vector2f to PyObject* -static PyObject* sfVector2f_to_PyObject(sf::Vector2f vector) { - return Py_BuildValue("(ff)", vector.x, vector.y); -} - -// Utility function to convert PyObject* to sf::Vector2f -static sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) { - float x, y; - if (!PyArg_ParseTuple(obj, "ff", &x, &y)) { - return sf::Vector2f(); // TODO / reconsider this default: Return default vector on parse error - } - return sf::Vector2f(x, y); -} - -// Utility function to convert UIGridPointState to PyObject* -static PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) { - PyObject* obj = PyObject_New(PyObject, &PyUIGridPointStateType); - if (!obj) return PyErr_NoMemory(); - - // Assuming PyUIGridPointStateObject structure has a UIGridPointState* member called 'data' - //((PyUIGridPointStateObject*)obj)->data = new UIGridPointState(state); // Copy constructor // wontimplement / feat - don't use new, get shared_ptr working - - return obj; -} - -// Function to convert std::vector to a Python list TODO need a PyUICollection style iterable -static PyObject* UIGridPointStateVector_to_PyList(const std::vector& vec) { - PyObject* list = PyList_New(vec.size()); - if (!list) return PyErr_NoMemory(); - - for (size_t i = 0; i < vec.size(); ++i) { - PyObject* obj = UIGridPointState_to_PyObject(vec[i]); - if (!obj) { // Cleanup on failure - Py_DECREF(list); - return NULL; - } - PyList_SET_ITEM(list, i, obj); // This steals a reference to obj - } - - return list; -} - -static PyObject* PyUIEntity_get_position(PyUIEntityObject* self, void* closure) { - return sfVector2f_to_PyObject(self->data->position); -} - -static int PyUIEntity_set_position(PyUIEntityObject* self, PyObject* value, void* closure) { - self->data->position = PyObject_to_sfVector2f(value); - return 0; -} - -static PyObject* PyUIEntity_get_gridstate(PyUIEntityObject* self, void* closure) { - // Assuming a function to convert std::vector to PyObject* list - return UIGridPointStateVector_to_PyList(self->data->gridstate); -} - -static PyObject* PyUIEntity_get_spritenumber(PyUIEntityObject* self, void* closure) { - return PyLong_FromDouble(self->data->sprite.getSpriteIndex()); -} - -static int PyUIEntity_set_spritenumber(PyUIEntityObject* self, PyObject* value, void* closure) { - int val; - if (PyLong_Check(value)) - val = PyLong_AsLong(value); - else - { - PyErr_SetString(PyExc_TypeError, "Value must be an integer."); - return -1; - } - //self->data->sprite.sprite_index = val; - self->data->sprite.setSpriteIndex(val); // todone - I don't like ".sprite.sprite" in this stack of UIEntity.UISprite.sf::Sprite - return 0; -} - -static PyObject* PyUIEntity_at(PyUIEntityObject* self, PyObject* o) -{ - int x, y; - if (!PyArg_ParseTuple(o, "ii", &x, &y)) { - PyErr_SetString(PyExc_TypeError, "UIEntity.at requires two integer arguments: (x, y)"); - return NULL; - } - - if (self->data->grid == NULL) { - PyErr_SetString(PyExc_ValueError, "Entity cannot access surroundings because it is not associated with a grid"); - return NULL; - } - - PyUIGridPointStateObject* obj = (PyUIGridPointStateObject*)((&PyUIGridPointStateType)->tp_alloc(&PyUIGridPointStateType, 0)); - //auto target = std::static_pointer_cast(target); - obj->data = &(self->data->gridstate[y + self->data->grid->grid_x * x]); - obj->grid = self->data->grid; - obj->entity = self->data; - return (PyObject*)obj; -} - -static PyMethodDef PyUIEntity_methods[] = { - {"at", (PyCFunction)PyUIEntity_at, METH_O}, - {NULL, NULL, 0, NULL} -}; - -// Define getters and setters -static PyGetSetDef PyUIEntity_getsetters[] = { - {"position", (getter)PyUIEntity_get_position, (setter)PyUIEntity_set_position, "Entity position", NULL}, - {"gridstate", (getter)PyUIEntity_get_gridstate, NULL, "Grid point states for the entity", NULL}, - {"sprite_number", (getter)PyUIEntity_get_spritenumber, (setter)PyUIEntity_set_spritenumber, "Sprite number (index) on the texture on the display", NULL}, - {NULL} /* Sentinel */ -}; - -static int PyUIEntity_init(PyUIEntityObject*, PyObject*, PyObject*); // forward declare - - - -// Define the PyTypeObject for UIEntity -static PyTypeObject PyUIEntityType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.Entity", - .tp_basicsize = sizeof(PyUIEntityObject), - .tp_itemsize = 0, - // Methods omitted for brevity - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = "UIEntity objects", - .tp_methods = PyUIEntity_methods, - .tp_getset = PyUIEntity_getsetters, - .tp_init = (initproc)PyUIEntity_init, - .tp_new = PyType_GenericNew, -}; - - - /* - * - * end PyUIEntity defs - * - */ - - /* - * - * PyUIGrid defs - * - */ - -static int PyUIGrid_init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { - int grid_x, grid_y; - PyObject* textureObj; - float box_x, box_y, box_w, box_h; - - if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) { - return -1; // If parsing fails, return an error - } - - // Convert PyObject texture to IndexTexture* - // This requires the texture object to have been initialized similar to UISprite's texture handling - if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) { - PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); - return -1; - } - PyTextureObject* pyTexture = reinterpret_cast(textureObj); - // TODO (7DRL day 2, item 4.) use shared_ptr / PyTextureObject on UIGrid - //IndexTexture* texture = pyTexture->data.get(); - - // Initialize UIGrid - //self->data = new UIGrid(grid_x, grid_y, texture, sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h)); - self->data = std::make_shared(grid_x, grid_y, pyTexture->data, - sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h)); - return 0; // Success -} - -static PyObject* PyUIGrid_get_grid_size(PyUIGridObject* self, void* closure) { - return Py_BuildValue("(ii)", self->data->grid_x, self->data->grid_y); -} - -static PyObject* PyUIGrid_get_position(PyUIGridObject* self, void* closure) { - auto& box = self->data->box; - return Py_BuildValue("(ff)", box.getPosition().x, box.getPosition().y); -} - -static int PyUIGrid_set_position(PyUIGridObject* self, PyObject* value, void* closure) { - float x, y; - if (!PyArg_ParseTuple(value, "ff", &x, &y)) { - PyErr_SetString(PyExc_ValueError, "Position must be a tuple of two floats"); - return -1; - } - self->data->box.setPosition(x, y); - return 0; -} - -static PyObject* PyUIGrid_get_size(PyUIGridObject* self, void* closure) { - auto& box = self->data->box; - return Py_BuildValue("(ff)", box.getSize().x, box.getSize().y); -} - -static int PyUIGrid_set_size(PyUIGridObject* self, PyObject* value, void* closure) { - float w, h; - if (!PyArg_ParseTuple(value, "ff", &w, &h)) { - PyErr_SetString(PyExc_ValueError, "Size must be a tuple of two floats"); - return -1; - } - self->data->box.setSize(sf::Vector2f(w, h)); - return 0; -} - -static PyObject* PyUIGrid_get_center(PyUIGridObject* self, void* closure) { - return Py_BuildValue("(ff)", self->data->center_x, self->data->center_y); -} - -static int PyUIGrid_set_center(PyUIGridObject* self, PyObject* value, void* closure) { - float x, y; - if (!PyArg_ParseTuple(value, "ff", &x, &y)) { - PyErr_SetString(PyExc_ValueError, "Size must be a tuple of two floats"); - return -1; - } - self->data->center_x = x; - self->data->center_y = y; - return 0; -} - -static PyObject* PyUIGrid_get_float_member(PyUIGridObject* self, void* closure) -{ - auto member_ptr = reinterpret_cast(closure); - if (member_ptr == 0) // x - return PyFloat_FromDouble(self->data->box.getPosition().x); - else if (member_ptr == 1) // y - return PyFloat_FromDouble(self->data->box.getPosition().y); - else if (member_ptr == 2) // w - return PyFloat_FromDouble(self->data->box.getSize().x); - else if (member_ptr == 3) // h - return PyFloat_FromDouble(self->data->box.getSize().y); - else if (member_ptr == 4) // center_x - return PyFloat_FromDouble(self->data->center_x); - else if (member_ptr == 5) // center_y - return PyFloat_FromDouble(self->data->center_y); - else if (member_ptr == 6) // zoom - return PyFloat_FromDouble(self->data->zoom); - else - { - PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); - return nullptr; - } -} - - -static int PyUIGrid_set_float_member(PyUIGridObject* self, PyObject* value, void* closure) -{ - float val; - auto member_ptr = reinterpret_cast(closure); - if (PyFloat_Check(value)) - { - val = PyFloat_AsDouble(value); - } - else if (PyLong_Check(value)) - { - val = PyLong_AsLong(value); - } - else - { - PyErr_SetString(PyExc_TypeError, "Value must be a floating point number."); - return -1; - } - if (member_ptr == 0) // x - self->data->box.setPosition(val, self->data->box.getPosition().y); - else if (member_ptr == 1) // y - self->data->box.setPosition(self->data->box.getPosition().x, val); - else if (member_ptr == 2) // w - self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y)); - else if (member_ptr == 3) // h - self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val)); - else if (member_ptr == 4) // center_x - self->data->center_x = val; - else if (member_ptr == 5) // center_y - self->data->center_y = val; - else if (member_ptr == 6) // zoom - self->data->zoom = val; - return 0; -} -// TODO (7DRL Day 2, item 5.) return Texture object -/* -static PyObject* PyUIGrid_get_texture(PyUIGridObject* self, void* closure) { - Py_INCREF(self->texture); - return self->texture; -} -*/ -static PyObject* PyUIGrid_get_texture(PyUIGridObject* self, void* closure) { - //return self->data->getTexture()->pyObject(); - PyTextureObject* obj = (PyTextureObject*)((&PyTextureType)->tp_alloc(&PyTextureType, 0)); - obj->data = self->data->getTexture(); - return (PyObject*)obj; -} - -static PyObject* PyUIGrid_at(PyUIGridObject* self, PyObject* o) -{ - int x, y; - if (!PyArg_ParseTuple(o, "ii", &x, &y)) { - PyErr_SetString(PyExc_TypeError, "UIGrid.at requires two integer arguments: (x, y)"); - return NULL; - } - if (x < 0 || x >= self->data->grid_x) { - PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_y)"); - return NULL; - } - if (y < 0 || y >= self->data->grid_y) { - PyErr_SetString(PyExc_ValueError, "y value out of range (0, Grid.grid_y)"); - return NULL; - } - - PyUIGridPointObject* obj = (PyUIGridPointObject*)((&PyUIGridPointType)->tp_alloc(&PyUIGridPointType, 0)); - //auto target = std::static_pointer_cast(target); - obj->data = &(self->data->points[x + self->data->grid_x * y]); - obj->grid = self->data; - return (PyObject*)obj; -} - -static PyMethodDef PyUIGrid_methods[] = { - {"at", (PyCFunction)PyUIGrid_at, METH_O}, - {NULL, NULL, 0, NULL} -}; - -static PyObject* PyUIGrid_get_children(PyUIGridObject* self, void* closure); // forward declare - -static PyGetSetDef PyUIGrid_getsetters[] = { - - // TODO - refactor into get_vector_member with field identifier values `(void*)n` - {"grid_size", (getter)PyUIGrid_get_grid_size, NULL, "Grid dimensions (grid_x, grid_y)", NULL}, - {"position", (getter)PyUIGrid_get_position, (setter)PyUIGrid_set_position, "Position of the grid (x, y)", NULL}, - {"size", (getter)PyUIGrid_get_size, (setter)PyUIGrid_set_size, "Size of the grid (width, height)", NULL}, - {"center", (getter)PyUIGrid_get_center, (setter)PyUIGrid_set_center, "Grid coordinate at the center of the Grid's view (pan)", NULL}, - - {"entities", (getter)PyUIGrid_get_children, NULL, "EntityCollection of entities on this grid", NULL}, - - {"x", (getter)PyUIGrid_get_float_member, (setter)PyUIGrid_set_float_member, "top-left corner X-coordinate", (void*)0}, - {"y", (getter)PyUIGrid_get_float_member, (setter)PyUIGrid_set_float_member, "top-left corner Y-coordinate", (void*)1}, - {"w", (getter)PyUIGrid_get_float_member, (setter)PyUIGrid_set_float_member, "visible widget width", (void*)2}, - {"h", (getter)PyUIGrid_get_float_member, (setter)PyUIGrid_set_float_member, "visible widget height", (void*)3}, - {"center_x", (getter)PyUIGrid_get_float_member, (setter)PyUIGrid_set_float_member, "center of the view X-coordinate", (void*)4}, - {"center_y", (getter)PyUIGrid_get_float_member, (setter)PyUIGrid_set_float_member, "center of the view Y-coordinate", (void*)5}, - {"zoom", (getter)PyUIGrid_get_float_member, (setter)PyUIGrid_set_float_member, "zoom factor for displaying the Grid", (void*)6}, - - {"click", (getter)PyUIDrawable_get_click, (setter)PyUIDrawable_set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIGRID}, - - {"texture", (getter)PyUIGrid_get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5 - {NULL} /* Sentinel */ -}; - - -/* // TODO standard pointer would need deleted, but I opted for a shared pointer. tp_dealloc currently not even defined in the PyTypeObject -static void PyUIGrid_dealloc(PyUIGridObject* self) { - delete self->data; // Clean up the allocated UIGrid object - Py_TYPE(self)->tp_free((PyObject*)self); -} -*/ - - static PyTypeObject PyUIGridType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.Grid", - .tp_basicsize = sizeof(PyUIGridObject), - .tp_itemsize = 0, - //.tp_dealloc = (destructor)[](PyObject* self) - //{ - // PyUIGridObject* obj = (PyUIGridObject*)self; - // obj->data.reset(); - // Py_TYPE(self)->tp_free(self); - //}, - //TODO - PyUIGrid REPR def: - // .tp_repr = (reprfunc)PyUIGrid_repr, - //.tp_hash = NULL, - //.tp_iter - //.tp_iternext - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("docstring"), - .tp_methods = PyUIGrid_methods, - //.tp_members = PyUIGrid_members, - .tp_getset = PyUIGrid_getsetters, - //.tp_base = NULL, - .tp_init = (initproc)PyUIGrid_init, - .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* - { - PyUIGridObject* self = (PyUIGridObject*)type->tp_alloc(type, 0); - if (self) self->data = std::make_shared(); - return (PyObject*)self; - } - }; - - - /* - * - * end PyUIGrid defs - * - */ - -// PyUIEntity_init defined here because it depends on the PyUIGridType (to accept grid optional keyword argument) -static int PyUIEntity_init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { - static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr }; - float x = 0.0f, y = 0.0f, scale = 1.0f; - int sprite_index = -1; - PyObject* texture = NULL; - PyObject* grid = NULL; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O", - const_cast(keywords), &x, &y, &texture, &sprite_index, &grid)) - { - return -1; - } - - // check types for texture - // - // Set Texture - // - if (texture != NULL && !PyObject_IsInstance(texture, (PyObject*)&PyTextureType)){ - PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); - 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*)&PyUIGridType)) { - 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(pytexture->data, sprite_index, sf::Vector2f(0,0), 1.0); - self->data->position = sf::Vector2f(x, y); - if (grid != NULL) { - PyUIGridObject* pygrid = (PyUIGridObject*)grid; - self->data->grid = pygrid->data; - // todone - on creation of Entity with Grid assignment, also append it to the entity list - pygrid->data->entities->push_back(self->data); - } - return 0; -} - -/* - * - * Begin PyUIEntityCollectionIter defs - * - */ - typedef struct { - PyObject_HEAD - std::shared_ptr>> data; - int index; - int start_size; - } PyUIEntityCollectionIterObject; - - static int PyUIEntityCollectionIter_init(PyUIEntityCollectionIterObject* self, PyObject* args, PyObject* kwds) - { - PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); - return -1; - } - - static PyObject* PyUIEntityCollectionIter_next(PyUIEntityCollectionIterObject* self) - { - if (self->data->size() != self->start_size) - { - PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration"); - return NULL; - } - - if (self->index > self->start_size - 1) - { - PyErr_SetNone(PyExc_StopIteration); - return NULL; - } - self->index++; - auto vec = self->data.get(); - if (!vec) - { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return NULL; - } - // Advance list iterator since Entities are not stored in a vector (if this code even worked) - // vectors only: //auto target = (*vec)[self->index-1]; - //auto l_front = (*vec).begin(); - //std::advance(l_front, self->index-1); - - // TODO build PyObject* of the correct UIDrawable subclass to return - //return py_instance(target); - return NULL; - } - - static PyObject* PyUIEntityCollectionIter_repr(PyUIEntityCollectionIterObject* self) - { - std::ostringstream ss; - if (!self->data) ss << ""; - else { - ss << "data->size() << " child objects, @ index " << self->index << ")>"; - } - std::string repr_str = ss.str(); - return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); - } - static PyTypeObject PyUIEntityCollectionIterType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.UICollectionIter", - .tp_basicsize = sizeof(PyUIEntityCollectionIterObject), - .tp_itemsize = 0, - .tp_dealloc = (destructor)[](PyObject* self) - { - PyUIEntityCollectionIterObject* obj = (PyUIEntityCollectionIterObject*)self; - obj->data.reset(); - Py_TYPE(self)->tp_free(self); - }, - .tp_repr = (reprfunc)PyUIEntityCollectionIter_repr, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("Iterator for a collection of UI objects"), - .tp_iternext = (iternextfunc)PyUIEntityCollectionIter_next, - //.tp_getset = PyUIEntityCollection_getset, - .tp_init = (initproc)PyUIEntityCollectionIter_init, // just raise an exception - .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* - { - PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); - return NULL; - } - }; - - /* - * - * End PyUIEntityCollectionIter defs - * - */ - -/* - * - * Begin PyUIEntityCollection defs - * - */ - typedef struct { - PyObject_HEAD - std::shared_ptr>> data; - std::shared_ptr grid; - } PyUIEntityCollectionObject; - - static Py_ssize_t PyUIEntityCollection_len(PyUIEntityCollectionObject* self) { - return self->data->size(); - } - - static PyObject* PyUIEntityCollection_getitem(PyUIEntityCollectionObject* self, Py_ssize_t index) { - // build a Python version of item at self->data[index] - // Copy pasted:: - auto vec = self->data.get(); - if (!vec) - { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return NULL; - } - while (index < 0) index += self->data->size(); - if (index > self->data->size() - 1) - { - PyErr_SetString(PyExc_IndexError, "EntityCollection index out of range"); - return NULL; - } - auto l_begin = (*vec).begin(); - std::advance(l_begin, index); - auto target = *l_begin; //auto target = (*vec)[index]; - //RET_PY_INSTANCE(target); - // construct and return an entity object that points directly into the UIGrid's entity vector - PyUIEntityObject* o = (PyUIEntityObject*)((&PyUIEntityType)->tp_alloc(&PyUIEntityType, 0)); - auto p = std::static_pointer_cast(target); - o->data = p; - return (PyObject*)o; - return NULL; - - - } - - static PySequenceMethods PyUIEntityCollection_sqmethods = { - .sq_length = (lenfunc)PyUIEntityCollection_len, - .sq_item = (ssizeargfunc)PyUIEntityCollection_getitem, - //.sq_item_by_index = PyUIEntityCollection_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) - }; - - static PyObject* PyUIEntityCollection_append(PyUIEntityCollectionObject* self, PyObject* o) - { - // if not UIDrawable subclass, reject it - // self->data->push_back( c++ object inside o ); - - // this would be a great use case for .tp_base - if (!PyObject_IsInstance(o, (PyObject*)&PyUIEntityType)) - { - PyErr_SetString(PyExc_TypeError, "Only Entity objects can be added to EntityCollection"); - return NULL; - } - PyUIEntityObject* entity = (PyUIEntityObject*)o; - self->data->push_back(entity->data); - entity->data->grid = self->grid; - - Py_INCREF(Py_None); - return Py_None; - } - static PyObject* PyUIEntityCollection_remove(PyUIEntityCollectionObject* self, PyObject* o) - { - if (!PyLong_Check(o)) - { - PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove"); - return NULL; - } - long index = PyLong_AsLong(o); - 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 correct part of the list - self->data->erase(std::next(self->data->begin(), index)); - Py_INCREF(Py_None); - return Py_None; - } - - static PyMethodDef PyUIEntityCollection_methods[] = { - {"append", (PyCFunction)PyUIEntityCollection_append, METH_O}, - //{"extend", (PyCFunction)PyUIEntityCollection_extend, METH_O}, // TODO - {"remove", (PyCFunction)PyUIEntityCollection_remove, METH_O}, - {NULL, NULL, 0, NULL} - }; - - static PyObject* PyUIEntityCollection_repr(PyUIEntityCollectionObject* self) - { - std::ostringstream ss; - if (!self->data) ss << ""; - else { - ss << "data->size() << " child objects)>"; - } - std::string repr_str = ss.str(); - return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); - } - - static int PyUIEntityCollection_init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds) - { - PyErr_SetString(PyExc_TypeError, "EntityCollection cannot be instantiated: a C++ data source is required."); - return -1; - } - - static PyObject* PyUIEntityCollection_iter(PyUIEntityCollectionObject* self) - { - PyUIEntityCollectionIterObject* iterObj; - iterObj = (PyUIEntityCollectionIterObject*)PyUIEntityCollectionIterType.tp_alloc(&PyUIEntityCollectionIterType, 0); - if (iterObj == NULL) { - return NULL; // Failed to allocate memory for the iterator object - } - - iterObj->data = self->data; - iterObj->index = 0; - iterObj->start_size = self->data->size(); - - return (PyObject*)iterObj; - } - static PyTypeObject PyUIEntityCollectionType = { - //PyVarObject_/HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.EntityCollection", - .tp_basicsize = sizeof(PyUIEntityCollectionObject), - .tp_itemsize = 0, - .tp_dealloc = (destructor)[](PyObject* self) - { - PyUIEntityCollectionObject* obj = (PyUIEntityCollectionObject*)self; - obj->data.reset(); - Py_TYPE(self)->tp_free(self); - }, - .tp_repr = (reprfunc)PyUIEntityCollection_repr, - .tp_as_sequence = &PyUIEntityCollection_sqmethods, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"), - .tp_iter = (getiterfunc)PyUIEntityCollection_iter, - .tp_methods = PyUIEntityCollection_methods, // append, remove - //.tp_getset = PyUIEntityCollection_getset, - .tp_init = (initproc)PyUIEntityCollection_init, // just raise an exception - .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* - { - // Does PyUIEntityCollectionType need __new__ if it's not supposed to be instantiable by the user? - // Should I just raise an exception? Or is the uninitialized shared_ptr enough of a blocker? - PyErr_SetString(PyExc_TypeError, "EntityCollection cannot be instantiated: a C++ data source is required."); - return NULL; - } - }; - - // Grid's get_children def must follow the EntityCollection def - static PyObject* PyUIGrid_get_children(PyUIGridObject* self, void* closure) - { - // create PyUICollection instance pointing to self->data->children - PyUIEntityCollectionObject* o = (PyUIEntityCollectionObject*)PyUIEntityCollectionType.tp_alloc(&PyUIEntityCollectionType, 0); - if (o) { - o->data = self->data->entities; // todone. / BUGFIX - entities isn't a shared pointer on UIGrid, what to do? -- I made it a sp>> - o->grid = self->data; - } - return (PyObject*)o; - } - - /* - * - * End PyUIEntityCollection defs - * - */ - - - - /* - * - * Begin PyUICollectionIter defs - * - */ - typedef struct { - PyObject_HEAD - std::shared_ptr>> data; - int index; - int start_size; - } PyUICollectionIterObject; - - static int PyUICollectionIter_init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds) - { - PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); - return -1; - } - - static PyObject* PyUICollectionIter_next(PyUICollectionIterObject* self) - { - if (self->data->size() != self->start_size) - { - PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration"); - return NULL; - } - - if (self->index > self->start_size - 1) - { - PyErr_SetNone(PyExc_StopIteration); - return NULL; - } - self->index++; - auto vec = self->data.get(); - if (!vec) - { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return NULL; - } - auto target = (*vec)[self->index-1]; - // TODO build PyObject* of the correct UIDrawable subclass to return - //return py_instance(target); - return NULL; - } - - static PyObject* PyUICollectionIter_repr(PyUICollectionIterObject* self) - { - std::ostringstream ss; - if (!self->data) ss << ""; - else { - ss << "data->size() << " child objects, @ index " << self->index << ")>"; - } - std::string repr_str = ss.str(); - return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); - } - - static PyTypeObject PyUICollectionIterType = { - //PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.UICollectionIter", - .tp_basicsize = sizeof(PyUICollectionIterObject), - .tp_itemsize = 0, - .tp_dealloc = (destructor)[](PyObject* self) - { - PyUICollectionIterObject* obj = (PyUICollectionIterObject*)self; - obj->data.reset(); - Py_TYPE(self)->tp_free(self); - }, - .tp_repr = (reprfunc)PyUICollectionIter_repr, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("Iterator for a collection of UI objects"), - .tp_iternext = (iternextfunc)PyUICollectionIter_next, - //.tp_getset = PyUICollection_getset, - .tp_init = (initproc)PyUICollectionIter_init, // just raise an exception - .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* - { - PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); - return NULL; - } - }; - - /* - * - * End PyUICollectionIter defs - * - */ - - - /* - * - * Begin PyUICollection defs - * - */ - - static Py_ssize_t PyUICollection_len(PyUICollectionObject* self) { - return self->data->size(); - } - - static PyObject* PyUICollection_getitem(PyUICollectionObject* self, Py_ssize_t index) { - // build a Python version of item at self->data[index] - // Copy pasted:: - auto vec = self->data.get(); - if (!vec) - { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return NULL; - } - while (index < 0) index += self->data->size(); - if (index > self->data->size() - 1) - { - PyErr_SetString(PyExc_IndexError, "UICollection index out of range"); - return NULL; - } - auto target = (*vec)[index]; - RET_PY_INSTANCE(target); - return NULL; - - - } - - static PySequenceMethods PyUICollection_sqmethods = { - .sq_length = (lenfunc)PyUICollection_len, - .sq_item = (ssizeargfunc)PyUICollection_getitem, - //.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) - }; - - static PyObject* PyUICollection_append(PyUICollectionObject* self, PyObject* o) - { - // if not UIDrawable subclass, reject it - // self->data->push_back( c++ object inside o ); - - // this would be a great use case for .tp_base - if (!PyObject_IsInstance(o, (PyObject*)&PyUIFrameType) && - !PyObject_IsInstance(o, (PyObject*)&PyUISpriteType) && - !PyObject_IsInstance(o, (PyObject*)&PyUICaptionType) && - !PyObject_IsInstance(o, (PyObject*)&PyUIGridType) - ) - { - PyErr_SetString(PyExc_TypeError, "Only Frame, Caption, Sprite, and Grid objects can be added to UICollection"); - return NULL; - } - - if (PyObject_IsInstance(o, (PyObject*)&PyUIFrameType)) - { - PyUIFrameObject* frame = (PyUIFrameObject*)o; - self->data->push_back(frame->data); - } - if (PyObject_IsInstance(o, (PyObject*)&PyUICaptionType)) - { - PyUICaptionObject* caption = (PyUICaptionObject*)o; - self->data->push_back(caption->data); - } - if (PyObject_IsInstance(o, (PyObject*)&PyUISpriteType)) - { - PyUISpriteObject* sprite = (PyUISpriteObject*)o; - self->data->push_back(sprite->data); - } - if (PyObject_IsInstance(o, (PyObject*)&PyUIGridType)) - { - PyUIGridObject* grid = (PyUIGridObject*)o; - self->data->push_back(grid->data); - } - - Py_INCREF(Py_None); - return Py_None; - } - - static PyObject* PyUICollection_remove(PyUICollectionObject* self, PyObject* o) - { - if (!PyLong_Check(o)) - { - PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove"); - return NULL; - } - long index = PyLong_AsLong(o); - 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); - Py_INCREF(Py_None); - return Py_None; - } - - static PyMethodDef PyUICollection_methods[] = { - {"append", (PyCFunction)PyUICollection_append, METH_O}, - //{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO - {"remove", (PyCFunction)PyUICollection_remove, METH_O}, - {NULL, NULL, 0, NULL} - }; - - static PyObject* PyUICollection_repr(PyUICollectionObject* self) - { - std::ostringstream ss; - if (!self->data) ss << ""; - else { - ss << "data->size() << " child objects)>"; - } - std::string repr_str = ss.str(); - return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); - } - - static int PyUICollection_init(PyUICollectionObject* self, PyObject* args, PyObject* kwds) - { - PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); - return -1; - } - - static PyObject* PyUICollection_iter(PyUICollectionObject* self) - { - PyUICollectionIterObject* iterObj; - iterObj = (PyUICollectionIterObject*)PyUICollectionIterType.tp_alloc(&PyUICollectionIterType, 0); - if (iterObj == NULL) { - return NULL; // Failed to allocate memory for the iterator object - } - - iterObj->data = self->data; - iterObj->index = 0; - iterObj->start_size = self->data->size(); - - return (PyObject*)iterObj; - } - - /* - static PyGetSetDef PyUICollection_getsetters[] = { - {NULL} - }; - */ - - static PyTypeObject PyUICollectionType = { - //PyVarObject_/HEAD_INIT(NULL, 0) - .tp_name = "mcrfpy.UICollection", - .tp_basicsize = sizeof(PyUICollectionObject), - .tp_itemsize = 0, - .tp_dealloc = (destructor)[](PyObject* self) - { - PyUICollectionObject* obj = (PyUICollectionObject*)self; - obj->data.reset(); - Py_TYPE(self)->tp_free(self); - }, - .tp_repr = (reprfunc)PyUICollection_repr, - .tp_as_sequence = &PyUICollection_sqmethods, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"), - .tp_iter = (getiterfunc)PyUICollection_iter, - .tp_methods = PyUICollection_methods, // append, remove - //.tp_getset = PyUICollection_getset, - .tp_init = (initproc)PyUICollection_init, // just raise an exception - .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* - { - // Does PyUICollectionType need __new__ if it's not supposed to be instantiable by the user? - // Should I just raise an exception? Or is the uninitialized shared_ptr enough of a blocker? - PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); - return NULL; - } - }; - /* - * - * End PyUICollection defs - * - */ - - static PyObject* PyUIFrame_get_children(PyUIFrameObject* self, void* closure) - { - // create PyUICollection instance pointing to self->data->children - PyUICollectionObject* o = (PyUICollectionObject*)PyUICollectionType.tp_alloc(&PyUICollectionType, 0); - if (o) - o->data = self->data->children; - return (PyObject*)o; - } - - -} // namespace mcrfpydef +#include "UIDrawable.h" + +#include "UIFrame.h" +#include "UICaption.h" +#include "UISprite.h" +#include "UIGridPoint.h" +#include "UIEntity.h" +#include "UIGrid.h" +#include "UICollection.h" diff --git a/src/UIBase.h b/src/UIBase.h new file mode 100644 index 0000000..70a5872 --- /dev/null +++ b/src/UIBase.h @@ -0,0 +1,32 @@ +#pragma once + +class UIEntity; +typedef struct { + PyObject_HEAD + std::shared_ptr data; +} PyUIEntityObject; + +class UIFrame; +typedef struct { + PyObject_HEAD + std::shared_ptr data; +} PyUIFrameObject; + +class UICaption; +typedef struct { + PyObject_HEAD + std::shared_ptr data; + PyObject* font; +} PyUICaptionObject; + +class UIGrid; +typedef struct { + PyObject_HEAD + std::shared_ptr data; +} PyUIGridObject; + +class UISprite; +typedef struct { + PyObject_HEAD + std::shared_ptr data; +} PyUISpriteObject; diff --git a/src/UICaption.cpp b/src/UICaption.cpp new file mode 100644 index 0000000..21c9c36 --- /dev/null +++ b/src/UICaption.cpp @@ -0,0 +1,261 @@ +#include "UICaption.h" +#include "GameEngine.h" +#include "PyColor.h" +#include "PyVector.h" +#include "PyFont.h" + +UIDrawable* UICaption::click_at(sf::Vector2f point) +{ + if (click_callable) + { + if (text.getGlobalBounds().contains(point)) return this; + } + return NULL; +} + +void UICaption::render(sf::Vector2f offset) +{ + text.move(offset); + Resources::game->getWindow().draw(text); + text.move(-offset); +} + +PyObjectsEnum UICaption::derived_type() +{ + return PyObjectsEnum::UICAPTION; +} + +PyObject* UICaption::get_float_member(PyUICaptionObject* self, void* closure) +{ + auto member_ptr = reinterpret_cast(closure); + if (member_ptr == 0) + return PyFloat_FromDouble(self->data->text.getPosition().x); + else if (member_ptr == 1) + return PyFloat_FromDouble(self->data->text.getPosition().y); + else if (member_ptr == 4) + return PyFloat_FromDouble(self->data->text.getOutlineThickness()); + else if (member_ptr == 5) + return PyLong_FromLong(self->data->text.getCharacterSize()); + else + { + PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); + return nullptr; + } +} + +int UICaption::set_float_member(PyUICaptionObject* self, PyObject* value, void* closure) +{ + float val; + auto member_ptr = reinterpret_cast(closure); + if (PyFloat_Check(value)) + { + val = PyFloat_AsDouble(value); + } + else if (PyLong_Check(value)) + { + val = PyLong_AsLong(value); + } + else + { + PyErr_SetString(PyExc_TypeError, "Value must be an integer."); + return -1; + } + if (member_ptr == 0) //x + self->data->text.setPosition(val, self->data->text.getPosition().y); + else if (member_ptr == 1) //y + self->data->text.setPosition(self->data->text.getPosition().x, val); + else if (member_ptr == 4) //outline + self->data->text.setOutlineThickness(val); + else if (member_ptr == 5) // character size + self->data->text.setCharacterSize(val); + return 0; +} + +PyObject* UICaption::get_vec_member(PyUICaptionObject* self, void* closure) +{ + return PyVector(self->data->text.getPosition()).pyObject(); +} + +int UICaption::set_vec_member(PyUICaptionObject* self, PyObject* value, void* closure) +{ + self->data->text.setPosition(PyVector::fromPy(value)); + return 0; +} + +PyObject* UICaption::get_color_member(PyUICaptionObject* self, void* closure) +{ + // TODO: migrate this code to a switch statement - validate closure & return values in one tighter, more extensible structure + + // validate closure (should be impossible to be wrong, but it's thorough) + auto member_ptr = reinterpret_cast(closure); + if (member_ptr != 0 && member_ptr != 1) + { + PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); + return nullptr; + } + + // TODO: manually calling tp_alloc to create a PyColorObject seems like an antipattern + // fetch correct member data + sf::Color color; + + if (member_ptr == 0) + { + color = self->data->text.getFillColor(); + } + else if (member_ptr == 1) + { + color = self->data->text.getOutlineColor(); + } + + return PyColor(color).pyObject(); +} + +int UICaption::set_color_member(PyUICaptionObject* self, PyObject* value, void* closure) +{ + auto member_ptr = reinterpret_cast(closure); + //TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse + int r, g, b, a; + if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color") /*(PyObject*)&mcrfpydef::PyColorType)*/)) + { + // get value from mcrfpy.Color instance + auto c = ((PyColorObject*)value)->data; + r = c.r; g = c.g; b = c.b; a = c.a; + std::cout << "got " << int(r) << ", " << int(g) << ", " << int(b) << ", " << int(a) << std::endl; + } + else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4) + { + // reject non-Color, non-tuple value + PyErr_SetString(PyExc_TypeError, "Value must be a tuple of 3 or 4 integers or an mcrfpy.Color object."); + return -1; + } + else // get value from tuples + { + r = PyLong_AsLong(PyTuple_GetItem(value, 0)); + g = PyLong_AsLong(PyTuple_GetItem(value, 1)); + b = PyLong_AsLong(PyTuple_GetItem(value, 2)); + a = 255; + + if (PyTuple_Size(value) == 4) + { + a = PyLong_AsLong(PyTuple_GetItem(value, 3)); + } + } + + if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255) + { + PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255."); + return -1; + } + + if (member_ptr == 0) + { + self->data->text.setFillColor(sf::Color(r, g, b, a)); + } + else if (member_ptr == 1) + { + self->data->text.setOutlineColor(sf::Color(r, g, b, a)); + } + else + { + PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); + return -1; + } + + return 0; +} + + +//TODO: evaluate use of Resources::caption_buffer... can't I do this with a std::string? +PyObject* UICaption::get_text(PyUICaptionObject* self, void* closure) +{ + Resources::caption_buffer = self->data->text.getString(); + return PyUnicode_FromString(Resources::caption_buffer.c_str()); +} + +int UICaption::set_text(PyUICaptionObject* self, PyObject* value, void* closure) +{ + PyObject* s = PyObject_Str(value); + PyObject * temp_bytes = PyUnicode_AsEncodedString(s, "UTF-8", "strict"); // Owned reference + if (temp_bytes != NULL) { + Resources::caption_buffer = PyBytes_AS_STRING(temp_bytes); // Borrowed pointer + Py_DECREF(temp_bytes); + } + self->data->text.setString(Resources::caption_buffer); + return 0; +} + +PyGetSetDef UICaption::getsetters[] = { + {"x", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "X coordinate of top-left corner", (void*)0}, + {"y", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Y coordinate of top-left corner", (void*)1}, + {"pos", (getter)UICaption::get_vec_member, (setter)UICaption::set_vec_member, "(x, y) vector", (void*)0}, + //{"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2}, + //{"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3}, + {"outline", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Thickness of the border", (void*)4}, + {"fill_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Fill color of the text", (void*)0}, + {"outline_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Outline color of the text", (void*)1}, + //{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL}, + {"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL}, + {"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}, + {NULL} +}; + +PyObject* UICaption::repr(PyUICaptionObject* self) +{ + std::ostringstream ss; + if (!self->data) ss << ""; + else { + auto text = self->data->text; + auto fc = text.getFillColor(); + auto oc = text.getOutlineColor(); + ss << ""; + } + std::string repr_str = ss.str(); + return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); +} + +int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) +{ + using namespace mcrfpydef; + static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", nullptr }; + float x = 0.0f, y = 0.0f; + char* text; + PyObject* font, fill_color, outline_color; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOO", + const_cast(keywords), &x, &y, &text, &font, &fill_color, &outline_color)) + { + return -1; + } + + // check types for font, fill_color, outline_color + + std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl; + 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) + { + auto font_obj = (PyFontObject*)font; + self->data->text.setFont(font_obj->data->font); + self->font = font; + Py_INCREF(font); + } else + { + // default font + //self->data->text.setFont(Resources::game->getFont()); + } + + self->data->text.setPosition(sf::Vector2f(x, y)); + self->data->text.setString((std::string)text); + self->data->text.setFillColor(sf::Color(0,0,0,255)); + self->data->text.setOutlineColor(sf::Color(128,128,128,255)); + + return 0; +} + diff --git a/src/UICaption.h b/src/UICaption.h new file mode 100644 index 0000000..bf01a96 --- /dev/null +++ b/src/UICaption.h @@ -0,0 +1,62 @@ +#pragma once +#include "Common.h" +#include "Python.h" +#include "UIDrawable.h" + +class UICaption: public UIDrawable +{ +public: + sf::Text text; + void render(sf::Vector2f) override final; + PyObjectsEnum derived_type() override final; + virtual UIDrawable* click_at(sf::Vector2f point) override final; + + static PyObject* get_float_member(PyUICaptionObject* self, void* closure); + static int set_float_member(PyUICaptionObject* self, PyObject* value, void* closure); + static PyObject* get_vec_member(PyUICaptionObject* self, void* closure); + static int set_vec_member(PyUICaptionObject* self, PyObject* value, void* closure); + static PyObject* get_color_member(PyUICaptionObject* self, void* closure); + static int set_color_member(PyUICaptionObject* self, PyObject* value, void* closure); + static PyObject* get_text(PyUICaptionObject* self, void* closure); + static int set_text(PyUICaptionObject* self, PyObject* value, void* closure); + static PyGetSetDef getsetters[]; + static PyObject* repr(PyUICaptionObject* self); + static int init(PyUICaptionObject* self, PyObject* args, PyObject* kwds); + +}; + +namespace mcrfpydef { + static PyTypeObject PyUICaptionType = { + .tp_name = "mcrfpy.Caption", + .tp_basicsize = sizeof(PyUICaptionObject), + .tp_itemsize = 0, + // TODO - move tp_dealloc to .cpp file as static function (UICaption::dealloc) + .tp_dealloc = (destructor)[](PyObject* self) + { + PyUICaptionObject* obj = (PyUICaptionObject*)self; + // TODO - reevaluate with PyFont usage; UICaption does not own the font + // release reference to font object + if (obj->font) Py_DECREF(obj->font); + obj->data.reset(); + Py_TYPE(self)->tp_free(self); + }, + .tp_repr = (reprfunc)UICaption::repr, + //.tp_hash = NULL, + //.tp_iter + //.tp_iternext + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = PyDoc_STR("docstring"), + //.tp_methods = PyUIFrame_methods, + //.tp_members = PyUIFrame_members, + .tp_getset = UICaption::getsetters, + //.tp_base = NULL, + .tp_init = (initproc)UICaption::init, + // TODO - move tp_new to .cpp file as a static function (UICaption::new) + .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* + { + PyUICaptionObject* self = (PyUICaptionObject*)type->tp_alloc(type, 0); + if (self) self->data = std::make_shared(); + return (PyObject*)self; + } + }; +} diff --git a/src/UICollection.cpp b/src/UICollection.cpp new file mode 100644 index 0000000..7aa16cb --- /dev/null +++ b/src/UICollection.cpp @@ -0,0 +1,203 @@ +#include "UICollection.h" + +#include "UIFrame.h" +#include "UICaption.h" +#include "UISprite.h" +#include "UIGrid.h" +#include "McRFPy_API.h" + +using namespace mcrfpydef; + +int UICollectionIter::init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds) +{ + PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); + return -1; +} + +PyObject* UICollectionIter::next(PyUICollectionIterObject* self) +{ + if (self->data->size() != self->start_size) + { + PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration"); + return NULL; + } + + if (self->index > self->start_size - 1) + { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + self->index++; + auto vec = self->data.get(); + if (!vec) + { + PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); + return NULL; + } + auto target = (*vec)[self->index-1]; + // TODO build PyObject* of the correct UIDrawable subclass to return + //return py_instance(target); + return NULL; +} + +PyObject* UICollectionIter::repr(PyUICollectionIterObject* self) +{ + std::ostringstream ss; + if (!self->data) ss << ""; + else { + ss << "data->size() << " child objects, @ index " << self->index << ")>"; + } + std::string repr_str = ss.str(); + return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); +} + +Py_ssize_t UICollection::len(PyUICollectionObject* self) { + return self->data->size(); +} + +PyObject* UICollection::getitem(PyUICollectionObject* self, Py_ssize_t index) { + // build a Python version of item at self->data[index] + // Copy pasted:: + auto vec = self->data.get(); + if (!vec) + { + PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); + return NULL; + } + while (index < 0) index += self->data->size(); + if (index > self->data->size() - 1) + { + PyErr_SetString(PyExc_IndexError, "UICollection index out of range"); + return NULL; + } + auto target = (*vec)[index]; + RET_PY_INSTANCE(target); +return NULL; + + +} + +PySequenceMethods UICollection::sqmethods = { + .sq_length = (lenfunc)UICollection::len, + .sq_item = (ssizeargfunc)UICollection::getitem, + //.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 + +auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"); + +I never identified why `using namespace mcrfpydef;` doesn't solve the segfault issue. +The horrible macro in UIDrawable was originally a workaround for this, but as I interact with the types outside of the monster UI.h, a more general (and less icky) solution is required. + +*/ + +PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o) +{ + // if not UIDrawable subclass, reject it + // self->data->push_back( c++ object inside o ); + + // 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")) && + !PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) && + !PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")) + ) + { + PyErr_SetString(PyExc_TypeError, "Only Frame, Caption, Sprite, and Grid objects can be added to UICollection"); + return NULL; + } + + if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) + { + PyUIFrameObject* frame = (PyUIFrameObject*)o; + self->data->push_back(frame->data); + } + if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) + { + PyUICaptionObject* caption = (PyUICaptionObject*)o; + self->data->push_back(caption->data); + } + if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) + { + PyUISpriteObject* sprite = (PyUISpriteObject*)o; + self->data->push_back(sprite->data); + } + if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) + { + PyUIGridObject* grid = (PyUIGridObject*)o; + self->data->push_back(grid->data); + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o) +{ + if (!PyLong_Check(o)) + { + PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove"); + return NULL; + } + long index = PyLong_AsLong(o); + 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); + Py_INCREF(Py_None); + return Py_None; +} + +PyMethodDef UICollection::methods[] = { + {"append", (PyCFunction)UICollection::append, METH_O}, + //{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO + {"remove", (PyCFunction)UICollection::remove, METH_O}, + {NULL, NULL, 0, NULL} +}; + +PyObject* UICollection::repr(PyUICollectionObject* self) +{ + std::ostringstream ss; + if (!self->data) ss << ""; + else { + ss << "data->size() << " child objects)>"; + } + std::string repr_str = ss.str(); + return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); +} + +int UICollection::init(PyUICollectionObject* self, PyObject* args, PyObject* kwds) +{ + PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); + return -1; +} + +PyObject* UICollection::iter(PyUICollectionObject* self) +{ + PyUICollectionIterObject* iterObj; + iterObj = (PyUICollectionIterObject*)PyUICollectionIterType.tp_alloc(&PyUICollectionIterType, 0); + if (iterObj == NULL) { + return NULL; // Failed to allocate memory for the iterator object + } + + iterObj->data = self->data; + iterObj->index = 0; + iterObj->start_size = self->data->size(); + + return (PyObject*)iterObj; +} diff --git a/src/UICollection.h b/src/UICollection.h new file mode 100644 index 0000000..cf4b559 --- /dev/null +++ b/src/UICollection.h @@ -0,0 +1,88 @@ +#pragma once +#include "Common.h" +#include "Python.h" + +#include "UIDrawable.h" + +class UICollectionIter +{ + // really more of a namespace: all the members are public and static. But being consistent with other UI objects +public: + static int init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds); + static PyObject* next(PyUICollectionIterObject* self); + static PyObject* repr(PyUICollectionIterObject* self); +}; + +class UICollection +{ + // really more of a namespace: all the members are public and static. But being consistent with other UI objects +public: + static Py_ssize_t len(PyUICollectionObject* self); + static PyObject* getitem(PyUICollectionObject* self, Py_ssize_t index); + static PySequenceMethods sqmethods; + static PyObject* append(PyUICollectionObject* self, PyObject* o); + static PyObject* remove(PyUICollectionObject* self, PyObject* o); + static PyMethodDef methods[]; + static PyObject* repr(PyUICollectionObject* self); + static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds); + static PyObject* iter(PyUICollectionObject* self); +}; + +namespace mcrfpydef { + static PyTypeObject PyUICollectionIterType = { + //PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.UICollectionIter", + .tp_basicsize = sizeof(PyUICollectionIterObject), + .tp_itemsize = 0, + //TODO - as static method, not inline lambda def, please + .tp_dealloc = (destructor)[](PyObject* self) + { + PyUICollectionIterObject* obj = (PyUICollectionIterObject*)self; + obj->data.reset(); + Py_TYPE(self)->tp_free(self); + }, + .tp_repr = (reprfunc)UICollectionIter::repr, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = PyDoc_STR("Iterator for a collection of UI objects"), + .tp_iternext = (iternextfunc)UICollectionIter::next, + //.tp_getset = PyUICollection_getset, + .tp_init = (initproc)UICollectionIter::init, // just raise an exception + //TODO - as static method, not inline lambda def, please + .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* + { + PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); + return NULL; + } + }; + + static PyTypeObject PyUICollectionType = { + //PyVarObject_/HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.UICollection", + .tp_basicsize = sizeof(PyUICollectionObject), + .tp_itemsize = 0, + //TODO - as static method, not inline lambda def, please + .tp_dealloc = (destructor)[](PyObject* self) + { + PyUICollectionObject* obj = (PyUICollectionObject*)self; + obj->data.reset(); + Py_TYPE(self)->tp_free(self); + }, + .tp_repr = (reprfunc)UICollection::repr, + .tp_as_sequence = &UICollection::sqmethods, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"), + .tp_iter = (getiterfunc)UICollection::iter, + .tp_methods = UICollection::methods, // append, remove + //.tp_getset = PyUICollection_getset, + .tp_init = (initproc)UICollection::init, // just raise an exception + //TODO - as static method, not inline lambda def, please + .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* + { + // Does PyUICollectionType need __new__ if it's not supposed to be instantiable by the user? + // Should I just raise an exception? Or is the uninitialized shared_ptr enough of a blocker? + PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); + return NULL; + } + }; + +} diff --git a/src/UIDrawable.cpp b/src/UIDrawable.cpp new file mode 100644 index 0000000..a710095 --- /dev/null +++ b/src/UIDrawable.cpp @@ -0,0 +1,81 @@ +#include "UIDrawable.h" +#include "UIFrame.h" +#include "UICaption.h" +#include "UISprite.h" +#include "UIGrid.h" + +UIDrawable::UIDrawable() { click_callable = NULL; } + +void UIDrawable::click_unregister() +{ + click_callable.reset(); +} + +void UIDrawable::render() +{ + render(sf::Vector2f()); +} + +PyObject* UIDrawable::get_click(PyObject* self, void* closure) { + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum + PyObject* ptr; + + switch (objtype) + { + case PyObjectsEnum::UIFRAME: + ptr = ((PyUIFrameObject*)self)->data->click_callable->borrow(); + break; + case PyObjectsEnum::UICAPTION: + ptr = ((PyUICaptionObject*)self)->data->click_callable->borrow(); + break; + case PyObjectsEnum::UISPRITE: + ptr = ((PyUISpriteObject*)self)->data->click_callable->borrow(); + break; + case PyObjectsEnum::UIGRID: + ptr = ((PyUIGridObject*)self)->data->click_callable->borrow(); + break; + default: + PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _get_click"); + return NULL; + } + if (ptr && ptr != Py_None) + return ptr; + else + return Py_None; +} + +int UIDrawable::set_click(PyObject* self, PyObject* value, void* closure) { + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum + UIDrawable* target; + switch (objtype) + { + case PyObjectsEnum::UIFRAME: + target = (((PyUIFrameObject*)self)->data.get()); + break; + case PyObjectsEnum::UICAPTION: + target = (((PyUICaptionObject*)self)->data.get()); + break; + case PyObjectsEnum::UISPRITE: + target = (((PyUISpriteObject*)self)->data.get()); + break; + case PyObjectsEnum::UIGRID: + target = (((PyUIGridObject*)self)->data.get()); + break; + default: + PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _set_click"); + return -1; + } + + if (value == Py_None) + { + target->click_unregister(); + } else { + target->click_register(value); + } + return 0; +} + +void UIDrawable::click_register(PyObject* callable) +{ + click_callable = std::make_unique(callable); +} diff --git a/src/UIDrawable.h b/src/UIDrawable.h new file mode 100644 index 0000000..4e636c5 --- /dev/null +++ b/src/UIDrawable.h @@ -0,0 +1,176 @@ +#pragma once +#include "Common.h" +#include "Python.h" +#include "structmember.h" +#include "IndexTexture.h" +#include "Resources.h" +#include + +#include "PyCallable.h" +#include "PyTexture.h" +#include "PyColor.h" +#include "PyVector.h" +#include "PyFont.h" + +#include "Resources.h" +#include "UIBase.h" +class UIFrame; class UICaption; class UISprite; class UIEntity; class UIGrid; + +enum PyObjectsEnum : int +{ + UIFRAME = 1, + UICAPTION, + UISPRITE, + UIGRID +}; + +class UIDrawable +{ +public: + void render(); + virtual void render(sf::Vector2f) = 0; + virtual PyObjectsEnum derived_type() = 0; + + // Mouse input handling - callable object, methods to find event's destination + std::unique_ptr click_callable; + virtual UIDrawable* click_at(sf::Vector2f point) = 0; + void click_register(PyObject*); + void click_unregister(); + + UIDrawable(); + + static PyObject* get_click(PyObject* self, void* closure); + static int set_click(PyObject* self, PyObject* value, void* closure); +}; + +typedef struct { + PyObject_HEAD + std::shared_ptr>> data; +} PyUICollectionObject; + +typedef struct { + PyObject_HEAD + std::shared_ptr>> data; + int index; + int start_size; +} PyUICollectionIterObject; + +namespace mcrfpydef { + //PyObject* py_instance(std::shared_ptr source); + // This function segfaults on tp_alloc for an unknown reason, but works inline with mcrfpydef:: methods. + +#define RET_PY_INSTANCE(target) { \ +switch (target->derived_type()) \ +{ \ + case PyObjectsEnum::UIFRAME: \ + { \ + PyUIFrameObject* o = (PyUIFrameObject*)((&PyUIFrameType)->tp_alloc(&PyUIFrameType, 0)); \ + if (o) \ + { \ + auto p = std::static_pointer_cast(target); \ + o->data = p; \ + auto utarget = o->data; \ + } \ + return (PyObject*)o; \ + } \ + case PyObjectsEnum::UICAPTION: \ + { \ + PyUICaptionObject* o = (PyUICaptionObject*)((&PyUICaptionType)->tp_alloc(&PyUICaptionType, 0)); \ + if (o) \ + { \ + auto p = std::static_pointer_cast(target); \ + o->data = p; \ + auto utarget = o->data; \ + } \ + return (PyObject*)o; \ + } \ + case PyObjectsEnum::UISPRITE: \ + { \ + PyUISpriteObject* o = (PyUISpriteObject*)((&PyUISpriteType)->tp_alloc(&PyUISpriteType, 0)); \ + if (o) \ + { \ + auto p = std::static_pointer_cast(target); \ + o->data = p; \ + auto utarget = o->data; \ + } \ + return (PyObject*)o; \ + } \ + case PyObjectsEnum::UIGRID: \ + { \ + PyUIGridObject* o = (PyUIGridObject*)((&PyUIGridType)->tp_alloc(&PyUIGridType, 0)); \ + if (o) \ + { \ + auto p = std::static_pointer_cast(target); \ + o->data = p; \ + auto utarget = o->data; \ + } \ + return (PyObject*)o; \ + } \ +} \ +} +// end macro definition + +//TODO: add this method to class scope; move implementation to .cpp file +/* +static PyObject* PyUIDrawable_get_click(PyObject* self, void* closure) { + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum + PyObject* ptr; + + switch (objtype) + { + case PyObjectsEnum::UIFRAME: + ptr = ((PyUIFrameObject*)self)->data->click_callable->borrow(); + break; + case PyObjectsEnum::UICAPTION: + ptr = ((PyUICaptionObject*)self)->data->click_callable->borrow(); + break; + case PyObjectsEnum::UISPRITE: + ptr = ((PyUISpriteObject*)self)->data->click_callable->borrow(); + break; + case PyObjectsEnum::UIGRID: + ptr = ((PyUIGridObject*)self)->data->click_callable->borrow(); + break; + default: + PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _get_click"); + return NULL; + } + if (ptr && ptr != Py_None) + return ptr; + else + return Py_None; +}*/ + +//TODO: add this method to class scope; move implementation to .cpp file +/* +static int PyUIDrawable_set_click(PyObject* self, PyObject* value, void* closure) { + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum + UIDrawable* target; + switch (objtype) + { + case PyObjectsEnum::UIFRAME: + target = (((PyUIFrameObject*)self)->data.get()); + break; + case PyObjectsEnum::UICAPTION: + target = (((PyUICaptionObject*)self)->data.get()); + break; + case PyObjectsEnum::UISPRITE: + target = (((PyUISpriteObject*)self)->data.get()); + break; + case PyObjectsEnum::UIGRID: + target = (((PyUIGridObject*)self)->data.get()); + break; + default: + PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _set_click"); + return -1; + } + + if (value == Py_None) + { + target->click_unregister(); + } else { + target->click_register(value); + } + return 0; +} +*/ +} diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp new file mode 100644 index 0000000..57aeeae --- /dev/null +++ b/src/UIEntity.cpp @@ -0,0 +1,165 @@ +#include "UIEntity.h" +#include "UIGrid.h" +#include "McRFPy_API.h" + +UIEntity::UIEntity() {} // this will not work lol. TODO remove default constructor by finding the shared pointer inits that use it + +UIEntity::UIEntity(UIGrid& grid) +: gridstate(grid.grid_x * grid.grid_y) +{ +} + +PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) { + int x, y; + if (!PyArg_ParseTuple(o, "ii", &x, &y)) { + PyErr_SetString(PyExc_TypeError, "UIEntity.at requires two integer arguments: (x, y)"); + return NULL; + } + + if (self->data->grid == NULL) { + PyErr_SetString(PyExc_ValueError, "Entity cannot access surroundings because it is not associated with a grid"); + return NULL; + } + /* + PyUIGridPointStateObject* obj = (PyUIGridPointStateObject*)((&mcrfpydef::PyUIGridPointStateType)->tp_alloc(&mcrfpydef::PyUIGridPointStateType, 0)); + */ + auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState"); + auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0); + //auto target = std::static_pointer_cast(target); + obj->data = &(self->data->gridstate[y + self->data->grid->grid_x * x]); + obj->grid = self->data->grid; + obj->entity = self->data; + return (PyObject*)obj; + +} + +int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { + static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr }; + float x = 0.0f, y = 0.0f, scale = 1.0f; + int sprite_index = -1; + PyObject* texture = NULL; + PyObject* grid = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O", + const_cast(keywords), &x, &y, &texture, &sprite_index, &grid)) + { + return -1; + } + + // check types for texture + // + // Set Texture + // + 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) // 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(pytexture->data, sprite_index, sf::Vector2f(0,0), 1.0); + self->data->position = sf::Vector2f(x, y); + if (grid != NULL) { + PyUIGridObject* pygrid = (PyUIGridObject*)grid; + self->data->grid = pygrid->data; + // todone - on creation of Entity with Grid assignment, also append it to the entity list + pygrid->data->entities->push_back(self->data); + } + return 0; +} + + + +PyObject* UIEntity::get_spritenumber(PyUIEntityObject* self, void* closure) { + return PyLong_FromDouble(self->data->sprite.getSpriteIndex()); +} + +PyObject* sfVector2f_to_PyObject(sf::Vector2f vector) { + return Py_BuildValue("(ff)", vector.x, vector.y); +} + +sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) { + float x, y; + if (!PyArg_ParseTuple(obj, "ff", &x, &y)) { + return sf::Vector2f(); // TODO / reconsider this default: Return default vector on parse error + } + return sf::Vector2f(x, y); +} + +// TODO - deprecate / remove this helper +PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) { + return PyObject_New(PyObject, (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState")); +} + +PyObject* UIGridPointStateVector_to_PyList(const std::vector& vec) { + PyObject* list = PyList_New(vec.size()); + if (!list) return PyErr_NoMemory(); + + for (size_t i = 0; i < vec.size(); ++i) { + PyObject* obj = UIGridPointState_to_PyObject(vec[i]); + if (!obj) { // Cleanup on failure + Py_DECREF(list); + return NULL; + } + PyList_SET_ITEM(list, i, obj); // This steals a reference to obj + } + + return list; +} + +PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) { + return sfVector2f_to_PyObject(self->data->position); +} + +int UIEntity::set_position(PyUIEntityObject* self, PyObject* value, void* closure) { + self->data->position = PyObject_to_sfVector2f(value); + return 0; +} + +PyObject* UIEntity::get_gridstate(PyUIEntityObject* self, void* closure) { + // Assuming a function to convert std::vector to PyObject* list + return UIGridPointStateVector_to_PyList(self->data->gridstate); +} + +int UIEntity::set_spritenumber(PyUIEntityObject* self, PyObject* value, void* closure) { + int val; + if (PyLong_Check(value)) + val = PyLong_AsLong(value); + else + { + PyErr_SetString(PyExc_TypeError, "Value must be an integer."); + return -1; + } + //self->data->sprite.sprite_index = val; + self->data->sprite.setSpriteIndex(val); // todone - I don't like ".sprite.sprite" in this stack of UIEntity.UISprite.sf::Sprite + return 0; +} + +PyMethodDef UIEntity::methods[] = { + {"at", (PyCFunction)UIEntity::at, METH_O}, + {NULL, NULL, 0, NULL} +}; + +PyGetSetDef UIEntity::getsetters[] = { + {"position", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position", NULL}, + {"gridstate", (getter)UIEntity::get_gridstate, NULL, "Grid point states for the entity", NULL}, + {"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite number (index) on the texture on the display", NULL}, + {NULL} /* Sentinel */ +}; diff --git a/src/UIEntity.h b/src/UIEntity.h new file mode 100644 index 0000000..852cbb3 --- /dev/null +++ b/src/UIEntity.h @@ -0,0 +1,74 @@ +#pragma once +#include "Common.h" +#include "Python.h" +#include "structmember.h" +#include "IndexTexture.h" +#include "Resources.h" +#include + +#include "PyCallable.h" +#include "PyTexture.h" +#include "PyColor.h" +#include "PyVector.h" +#include "PyFont.h" + +#include "UIGridPoint.h" +#include "UIDrawable.h" +#include "UIBase.h" +#include "UISprite.h" + +class UIGrid; + +//class UIEntity; +//typedef struct { +// PyObject_HEAD +// std::shared_ptr data; +//} PyUIEntityObject; + +// helper methods with no namespace requirement +static PyObject* sfVector2f_to_PyObject(sf::Vector2f vector); +static sf::Vector2f PyObject_to_sfVector2f(PyObject* obj); +static PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state); +static PyObject* UIGridPointStateVector_to_PyList(const std::vector& vec); + +// TODO: make UIEntity a drawable +class UIEntity//: public UIDrawable +{ +public: + //PyObject* self; + std::shared_ptr grid; + std::vector gridstate; + UISprite sprite; + sf::Vector2f position; //(x,y) in grid coordinates; float for animation + void render(sf::Vector2f); //override final; + + UIEntity(); + UIEntity(UIGrid&); + + static PyObject* at(PyUIEntityObject* self, PyObject* o); + static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds); + + static PyObject* get_position(PyUIEntityObject* self, void* closure); + static int set_position(PyUIEntityObject* self, PyObject* value, void* closure); + static PyObject* get_gridstate(PyUIEntityObject* self, void* closure); + static PyObject* get_spritenumber(PyUIEntityObject* self, void* closure); + static int set_spritenumber(PyUIEntityObject* self, PyObject* value, void* closure); + static PyMethodDef methods[]; + static PyGetSetDef getsetters[]; +}; + +namespace mcrfpydef { + static PyTypeObject PyUIEntityType = { + //PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.Entity", + .tp_basicsize = sizeof(PyUIEntityObject), + .tp_itemsize = 0, + // Methods omitted for brevity + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "UIEntity objects", + .tp_methods = UIEntity::methods, + .tp_getset = UIEntity::getsetters, + .tp_init = (initproc)UIEntity::init, + .tp_new = PyType_GenericNew, + }; +} diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp new file mode 100644 index 0000000..8c9561c --- /dev/null +++ b/src/UIFrame.cpp @@ -0,0 +1,265 @@ +#include "UIFrame.h" +#include "UICollection.h" +#include "GameEngine.h" + +UIDrawable* UIFrame::click_at(sf::Vector2f point) +{ + for (auto e: *children) + { + auto p = e->click_at(point + box.getPosition()); + if (p) + return p; + } + if (click_callable) + { + float x = box.getPosition().x, y = box.getPosition().y, w = box.getSize().x, h = box.getSize().y; + if (point.x > x && point.y > y && point.x < x+w && point.y < y+h) return this; + } + return NULL; +} + +UIFrame::UIFrame() +: outline(0) +{ + children = std::make_shared>>(); + box.setPosition(0, 0); + box.setSize(sf::Vector2f(0, 0)); +} + +UIFrame::UIFrame(float _x, float _y, float _w, float _h) +: outline(0) +{ + box.setPosition(_x, _y); + box.setSize(sf::Vector2f(_w, _h)); + children = std::make_shared>>(); +} + +UIFrame::~UIFrame() +{ + children.reset(); +} + +PyObjectsEnum UIFrame::derived_type() +{ + return PyObjectsEnum::UIFRAME; +} + +void UIFrame::render(sf::Vector2f offset) +{ + box.move(offset); + Resources::game->getWindow().draw(box); + box.move(-offset); + + for (auto drawable : *children) { + drawable->render(offset + box.getPosition()); + } +} + +PyObject* UIFrame::get_children(PyUIFrameObject* self, void* closure) +{ + // create PyUICollection instance pointing to self->data->children + //PyUICollectionObject* o = (PyUICollectionObject*)mcrfpydef::PyUICollectionType.tp_alloc(&mcrfpydef::PyUICollectionType, 0); + auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollection"); + auto o = (PyUICollectionObject*)type->tp_alloc(type, 0); + if (o) + o->data = self->data->children; + return (PyObject*)o; +} + + +PyObject* UIFrame::get_float_member(PyUIFrameObject* self, void* closure) +{ + auto member_ptr = reinterpret_cast(closure); + if (member_ptr == 0) + return PyFloat_FromDouble(self->data->box.getPosition().x); + else if (member_ptr == 1) + return PyFloat_FromDouble(self->data->box.getPosition().y); + else if (member_ptr == 2) + return PyFloat_FromDouble(self->data->box.getSize().x); + else if (member_ptr == 3) + return PyFloat_FromDouble(self->data->box.getSize().y); + else if (member_ptr == 4) + return PyFloat_FromDouble(self->data->box.getOutlineThickness()); + else + { + PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); + return nullptr; + } +} + +int UIFrame::set_float_member(PyUIFrameObject* self, PyObject* value, void* closure) +{ + float val; + auto member_ptr = reinterpret_cast(closure); + if (PyFloat_Check(value)) + { + val = PyFloat_AsDouble(value); + } + else if (PyLong_Check(value)) + { + val = PyLong_AsLong(value); + } + else + { + PyErr_SetString(PyExc_TypeError, "Value must be an integer."); + return -1; + } + if (member_ptr == 0) //x + self->data->box.setPosition(val, self->data->box.getPosition().y); + else if (member_ptr == 1) //y + self->data->box.setPosition(self->data->box.getPosition().x, val); + else if (member_ptr == 2) //w + self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y)); + else if (member_ptr == 3) //h + self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val)); + else if (member_ptr == 4) //outline + self->data->box.setOutlineThickness(val); + return 0; +} + +PyObject* UIFrame::get_color_member(PyUIFrameObject* self, void* closure) +{ + // validate closure (should be impossible to be wrong, but it's thorough) + auto member_ptr = reinterpret_cast(closure); + if (member_ptr != 0 && member_ptr != 1) + { + PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); + return nullptr; + } + //PyTypeObject* colorType = &PyColorType; + auto colorType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"); + PyObject* pyColor = colorType->tp_alloc(colorType, 0); + if (pyColor == NULL) + { + std::cout << "failure to allocate mcrfpy.Color / PyColorType" << std::endl; + return NULL; + } + PyColorObject* pyColorObj = reinterpret_cast(pyColor); + + // fetch correct member data + sf::Color color; + if (member_ptr == 0) + { + color = self->data->box.getFillColor(); + //return Py_BuildValue("(iii)", color.r, color.g, color.b); + } + else if (member_ptr == 1) + { + color = self->data->box.getOutlineColor(); + //return Py_BuildValue("(iii)", color.r, color.g, color.b); + } + + return PyColor(color).pyObject(); +} + +int UIFrame::set_color_member(PyUIFrameObject* self, PyObject* value, void* closure) +{ + //TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse + auto member_ptr = reinterpret_cast(closure); + int r, g, b, a; + if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"))) + { + sf::Color c = ((PyColorObject*)value)->data; + r = c.r; g = c.g; b = c.b; a = c.a; + } + else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4) + { + // reject non-Color, non-tuple value + PyErr_SetString(PyExc_TypeError, "Value must be a tuple of 3 or 4 integers or an mcrfpy.Color object."); + return -1; + } + else // get value from tuples + { + r = PyLong_AsLong(PyTuple_GetItem(value, 0)); + g = PyLong_AsLong(PyTuple_GetItem(value, 1)); + b = PyLong_AsLong(PyTuple_GetItem(value, 2)); + a = 255; + + if (PyTuple_Size(value) == 4) + { + a = PyLong_AsLong(PyTuple_GetItem(value, 3)); + } + } + + if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255) + { + PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255."); + return -1; + } + + if (member_ptr == 0) + { + self->data->box.setFillColor(sf::Color(r, g, b, a)); + } + else if (member_ptr == 1) + { + self->data->box.setOutlineColor(sf::Color(r, g, b, a)); + } + else + { + PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); + return -1; + } + + return 0; +} + +PyGetSetDef UIFrame::getsetters[] = { + {"x", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "X coordinate of top-left corner", (void*)0}, + {"y", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Y coordinate of top-left corner", (void*)1}, + {"w", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "width of the rectangle", (void*)2}, + {"h", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "height of the rectangle", (void*)3}, + {"outline", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Thickness of the border", (void*)4}, + {"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Fill color of the rectangle", (void*)0}, + {"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1}, + {"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}, + {NULL} +}; + +PyObject* UIFrame::repr(PyUIFrameObject* self) +{ + std::ostringstream ss; + if (!self->data) ss << ""; + else { + auto box = self->data->box; + auto fc = box.getFillColor(); + auto oc = box.getOutlineColor(); + ss << "data->children->size() << " child objects" << + ")>"; + } + std::string repr_str = ss.str(); + return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); +} + +int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds) +{ + //std::cout << "Init called\n"; + const char* keywords[] = { "x", "y", "w", "h", "fill_color", "outline_color", "outline", nullptr }; + float x = 0.0f, y = 0.0f, w = 0.0f, h=0.0f, outline=0.0f; + PyObject* fill_color = 0; + PyObject* outline_color = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffff|OOf", const_cast(keywords), &x, &y, &w, &h, &fill_color, &outline_color, &outline)) + { + return -1; + } + + self->data->box.setPosition(sf::Vector2f(x, y)); + self->data->box.setSize(sf::Vector2f(w, h)); + self->data->box.setOutlineThickness(outline); + // getsetter abuse because I haven't standardized Color object parsing (TODO) + int err_val = 0; + if (fill_color && fill_color != Py_None) err_val = UIFrame::set_color_member(self, fill_color, (void*)0); + else self->data->box.setFillColor(sf::Color(0,0,0,255)); + if (err_val) return err_val; + if (outline_color && outline_color != Py_None) err_val = UIFrame::set_color_member(self, outline_color, (void*)1); + else self->data->box.setOutlineColor(sf::Color(128,128,128,255)); + if (err_val) return err_val; + return 0; +} diff --git a/src/UIFrame.h b/src/UIFrame.h new file mode 100644 index 0000000..5875ad6 --- /dev/null +++ b/src/UIFrame.h @@ -0,0 +1,77 @@ +#pragma once +#include "Common.h" +#include "Python.h" +#include "structmember.h" +#include "IndexTexture.h" +#include "Resources.h" +#include + +#include "PyCallable.h" +#include "PyColor.h" +#include "PyVector.h" +#include "UIDrawable.h" +#include "UIBase.h" + +//class UIFrame; +// +//typedef struct { +// PyObject_HEAD +// std::shared_ptr data; +//} PyUIFrameObject; + +class UIFrame: public UIDrawable +{ +public: + UIFrame(float, float, float, float); + UIFrame(); + ~UIFrame(); + sf::RectangleShape box; + float outline; + std::shared_ptr>> children; + void render(sf::Vector2f) override final; + void move(sf::Vector2f); + PyObjectsEnum derived_type() override final; + virtual UIDrawable* click_at(sf::Vector2f point) override final; + + static PyObject* get_children(PyUIFrameObject* self, void* closure); + + static PyObject* get_float_member(PyUIFrameObject* self, void* closure); + static int set_float_member(PyUIFrameObject* self, PyObject* value, void* closure); + static PyObject* get_color_member(PyUIFrameObject* self, void* closure); + static int set_color_member(PyUIFrameObject* self, PyObject* value, void* closure); + static PyGetSetDef getsetters[]; + static PyObject* repr(PyUIFrameObject* self); + static int init(PyUIFrameObject* self, PyObject* args, PyObject* kwds); +}; + +namespace mcrfpydef { + static PyTypeObject PyUIFrameType = { + //PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.Frame", + .tp_basicsize = sizeof(PyUIFrameObject), + .tp_itemsize = 0, + .tp_dealloc = (destructor)[](PyObject* self) + { + PyUIFrameObject* obj = (PyUIFrameObject*)self; + obj->data.reset(); + Py_TYPE(self)->tp_free(self); + }, + .tp_repr = (reprfunc)UIFrame::repr, + //.tp_hash = NULL, + //.tp_iter + //.tp_iternext + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = PyDoc_STR("docstring"), + //.tp_methods = PyUIFrame_methods, + //.tp_members = PyUIFrame_members, + .tp_getset = UIFrame::getsetters, + //.tp_base = NULL, + .tp_init = (initproc)UIFrame::init, + .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* + { + PyUIFrameObject* self = (PyUIFrameObject*)type->tp_alloc(type, 0); + if (self) self->data = std::make_shared(); + return (PyObject*)self; + } + }; +} diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp new file mode 100644 index 0000000..4a0097d --- /dev/null +++ b/src/UIGrid.cpp @@ -0,0 +1,608 @@ +#include "UIGrid.h" +#include "GameEngine.h" +#include "McRFPy_API.h" + +UIGrid::UIGrid() {} + +UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, sf::Vector2f _xy, sf::Vector2f _wh) +: grid_x(gx), grid_y(gy), + zoom(1.0f), center_x((gx/2) * _ptex->sprite_width), center_y((gy/2) * _ptex->sprite_height), + ptex(_ptex), points(gx * gy) +{ + entities = std::make_shared>>(); + + box.setSize(_wh); + box.setPosition(_xy); + + box.setFillColor(sf::Color(0,0,0,0)); + // create renderTexture with maximum theoretical size; sprite can resize to show whatever amount needs to be rendered + renderTexture.create(1920, 1080); // TODO - renderTexture should be window size; above 1080p this will cause rendering errors + + sprite = ptex->sprite(0); + + output.setTextureRect( + sf::IntRect(0, 0, + box.getSize().x, box.getSize().y)); + output.setPosition(box.getPosition()); + // textures are upside-down inside renderTexture + output.setTexture(renderTexture.getTexture()); + +} + +void UIGrid::update() {} + + +void UIGrid::render(sf::Vector2f) +{ + output.setPosition(box.getPosition()); // output sprite can move; update position when drawing + // output size can change; update size when drawing + output.setTextureRect( + sf::IntRect(0, 0, + box.getSize().x, box.getSize().y)); + renderTexture.clear(sf::Color(8, 8, 8, 255)); // TODO - UIGrid needs a "background color" field + // sprites that are visible according to zoom, center_x, center_y, and box width + float center_x_sq = center_x / ptex->sprite_width; + float center_y_sq = center_y / ptex->sprite_height; + + float width_sq = box.getSize().x / (ptex->sprite_width * zoom); + float height_sq = box.getSize().y / (ptex->sprite_height * zoom); + float left_edge = center_x_sq - (width_sq / 2.0); + float top_edge = center_y_sq - (height_sq / 2.0); + + int left_spritepixels = center_x - (box.getSize().x / 2.0 / zoom); + int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom); + + //sprite.setScale(sf::Vector2f(zoom, zoom)); + sf::RectangleShape r; // for colors and overlays + r.setSize(sf::Vector2f(ptex->sprite_width * zoom, ptex->sprite_height * zoom)); + r.setOutlineThickness(0); + + int x_limit = left_edge + width_sq + 2; + if (x_limit > grid_x) x_limit = grid_x; + + int y_limit = top_edge + height_sq + 2; + if (y_limit > grid_y) y_limit = grid_y; + + // base layer - bottom color, tile sprite ("ground") + for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0); + x < x_limit; //x < view_width; + x+=1) + { + //for (float y = (top_edge >= 0 ? top_edge : 0); + for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0); + y < y_limit; //y < view_height; + y+=1) + { + auto pixel_pos = sf::Vector2f( + (x*ptex->sprite_width - left_spritepixels) * zoom, + (y*ptex->sprite_height - top_spritepixels) * zoom ); + + auto gridpoint = at(std::floor(x), std::floor(y)); + + //sprite.setPosition(pixel_pos); + + r.setPosition(pixel_pos); + r.setFillColor(gridpoint.color); + renderTexture.draw(r); + + // tilesprite + // if discovered but not visible, set opacity to 90% + // if not discovered... just don't draw it? + if (gridpoint.tilesprite != -1) { + sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);; + renderTexture.draw(sprite); + } + } + } + + // middle layer - entities + // disabling entity rendering until I can render their UISprite inside the rendertexture (not directly to window) + for (auto e : *entities) { + // TODO skip out-of-bounds entities (grid square not visible at all, check for partially on visible grid squares / floating point grid position) + //auto drawent = e->cGrid->indexsprite.drawable(); + auto& drawent = e->sprite; + //drawent.setScale(zoom, zoom); + drawent.setScale(sf::Vector2f(zoom, zoom)); + auto pixel_pos = sf::Vector2f( + (e->position.x*ptex->sprite_width - left_spritepixels) * zoom, + (e->position.y*ptex->sprite_height - top_spritepixels) * zoom ); + //drawent.setPosition(pixel_pos); + //renderTexture.draw(drawent); + drawent.render(pixel_pos, renderTexture); + } + + + // top layer - opacity for discovered / visible status (debug, basically) + /* // Disabled until I attach a "perspective" + for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0); + x < x_limit; //x < view_width; + x+=1) + { + //for (float y = (top_edge >= 0 ? top_edge : 0); + for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0); + y < y_limit; //y < view_height; + y+=1) + { + + auto pixel_pos = sf::Vector2f( + (x*itex->grid_size - left_spritepixels) * zoom, + (y*itex->grid_size - top_spritepixels) * zoom ); + + auto gridpoint = at(std::floor(x), std::floor(y)); + + sprite.setPosition(pixel_pos); + + r.setPosition(pixel_pos); + + // visible & discovered layers for testing purposes + if (!gridpoint.discovered) { + r.setFillColor(sf::Color(16, 16, 20, 192)); // 255 opacity for actual blackout + renderTexture.draw(r); + } else if (!gridpoint.visible) { + r.setFillColor(sf::Color(32, 32, 40, 128)); + renderTexture.draw(r); + } + + // overlay + + // uisprite + } + } + */ + + // grid lines for testing & validation + /* + sf::Vertex line[] = + { + sf::Vertex(sf::Vector2f(0, 0), sf::Color::Red), + sf::Vertex(box.getSize(), sf::Color::Red), + + }; + + renderTexture.draw(line, 2, sf::Lines); + sf::Vertex lineb[] = + { + sf::Vertex(sf::Vector2f(0, box.getSize().y), sf::Color::Blue), + sf::Vertex(sf::Vector2f(box.getSize().x, 0), sf::Color::Blue), + + }; + + renderTexture.draw(lineb, 2, sf::Lines); + */ + + // render to window + renderTexture.display(); + Resources::game->getWindow().draw(output); + +} + +UIGridPoint& UIGrid::at(int x, int y) +{ + return points[y * grid_x + x]; +} + +PyObjectsEnum UIGrid::derived_type() +{ + return PyObjectsEnum::UIGRID; +} + +std::shared_ptr UIGrid::getTexture() +{ + return ptex; +} + +UIDrawable* UIGrid::click_at(sf::Vector2f point) +{ + if (click_callable) + { + if(box.getGlobalBounds().contains(point)) return this; + } + return NULL; +} + + +int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { + int grid_x, grid_y; + PyObject* textureObj; + float box_x, box_y, box_w, box_h; + + if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) { + return -1; // If parsing fails, return an error + } + + // Convert PyObject texture to IndexTexture* + // This requires the texture object to have been initialized similar to UISprite's texture handling + + //if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) { + if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) { + PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); + return -1; + } + PyTextureObject* pyTexture = reinterpret_cast(textureObj); + // TODO (7DRL day 2, item 4.) use shared_ptr / PyTextureObject on UIGrid + //IndexTexture* texture = pyTexture->data.get(); + + // Initialize UIGrid + //self->data = new UIGrid(grid_x, grid_y, texture, sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h)); + self->data = std::make_shared(grid_x, grid_y, pyTexture->data, + sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h)); + return 0; // Success +} + +PyObject* UIGrid::get_grid_size(PyUIGridObject* self, void* closure) { + return Py_BuildValue("(ii)", self->data->grid_x, self->data->grid_y); +} + +PyObject* UIGrid::get_position(PyUIGridObject* self, void* closure) { + auto& box = self->data->box; + return Py_BuildValue("(ff)", box.getPosition().x, box.getPosition().y); +} + +int UIGrid::set_position(PyUIGridObject* self, PyObject* value, void* closure) { + float x, y; + if (!PyArg_ParseTuple(value, "ff", &x, &y)) { + PyErr_SetString(PyExc_ValueError, "Position must be a tuple of two floats"); + return -1; + } + self->data->box.setPosition(x, y); + return 0; +} + +PyObject* UIGrid::get_size(PyUIGridObject* self, void* closure) { + auto& box = self->data->box; + return Py_BuildValue("(ff)", box.getSize().x, box.getSize().y); +} + +int UIGrid::set_size(PyUIGridObject* self, PyObject* value, void* closure) { + float w, h; + if (!PyArg_ParseTuple(value, "ff", &w, &h)) { + PyErr_SetString(PyExc_ValueError, "Size must be a tuple of two floats"); + return -1; + } + self->data->box.setSize(sf::Vector2f(w, h)); + return 0; +} + +PyObject* UIGrid::get_center(PyUIGridObject* self, void* closure) { + return Py_BuildValue("(ff)", self->data->center_x, self->data->center_y); +} + +int UIGrid::set_center(PyUIGridObject* self, PyObject* value, void* closure) { + float x, y; + if (!PyArg_ParseTuple(value, "ff", &x, &y)) { + PyErr_SetString(PyExc_ValueError, "Size must be a tuple of two floats"); + return -1; + } + self->data->center_x = x; + self->data->center_y = y; + return 0; +} + +PyObject* UIGrid::get_float_member(PyUIGridObject* self, void* closure) +{ + auto member_ptr = reinterpret_cast(closure); + if (member_ptr == 0) // x + return PyFloat_FromDouble(self->data->box.getPosition().x); + else if (member_ptr == 1) // y + return PyFloat_FromDouble(self->data->box.getPosition().y); + else if (member_ptr == 2) // w + return PyFloat_FromDouble(self->data->box.getSize().x); + else if (member_ptr == 3) // h + return PyFloat_FromDouble(self->data->box.getSize().y); + else if (member_ptr == 4) // center_x + return PyFloat_FromDouble(self->data->center_x); + else if (member_ptr == 5) // center_y + return PyFloat_FromDouble(self->data->center_y); + else if (member_ptr == 6) // zoom + return PyFloat_FromDouble(self->data->zoom); + else + { + PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); + return nullptr; + } +} + +int UIGrid::set_float_member(PyUIGridObject* self, PyObject* value, void* closure) +{ + float val; + auto member_ptr = reinterpret_cast(closure); + if (PyFloat_Check(value)) + { + val = PyFloat_AsDouble(value); + } + else if (PyLong_Check(value)) + { + val = PyLong_AsLong(value); + } + else + { + PyErr_SetString(PyExc_TypeError, "Value must be a floating point number."); + return -1; + } + if (member_ptr == 0) // x + self->data->box.setPosition(val, self->data->box.getPosition().y); + else if (member_ptr == 1) // y + self->data->box.setPosition(self->data->box.getPosition().x, val); + else if (member_ptr == 2) // w + self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y)); + else if (member_ptr == 3) // h + self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val)); + else if (member_ptr == 4) // center_x + self->data->center_x = val; + else if (member_ptr == 5) // center_y + self->data->center_y = val; + else if (member_ptr == 6) // zoom + self->data->zoom = val; + return 0; +} +// TODO (7DRL Day 2, item 5.) return Texture object +/* +PyObject* UIGrid::get_texture(PyUIGridObject* self, void* closure) { + Py_INCREF(self->texture); + return self->texture; +} +*/ + +PyObject* UIGrid::get_texture(PyUIGridObject* self, void* closure) { + //return self->data->getTexture()->pyObject(); + // PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState") + //PyTextureObject* obj = (PyTextureObject*)((&PyTextureType)->tp_alloc(&PyTextureType, 0)); + auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"); + auto obj = (PyTextureObject*)type->tp_alloc(type, 0); + obj->data = self->data->getTexture(); + return (PyObject*)obj; +} + +PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* o) +{ + int x, y; + if (!PyArg_ParseTuple(o, "ii", &x, &y)) { + PyErr_SetString(PyExc_TypeError, "UIGrid.at requires two integer arguments: (x, y)"); + return NULL; + } + if (x < 0 || x >= self->data->grid_x) { + PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_y)"); + return NULL; + } + if (y < 0 || y >= self->data->grid_y) { + PyErr_SetString(PyExc_ValueError, "y value out of range (0, Grid.grid_y)"); + return NULL; + } + + //PyUIGridPointObject* obj = (PyUIGridPointObject*)((&PyUIGridPointType)->tp_alloc(&PyUIGridPointType, 0)); + auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPoint"); + auto obj = (PyUIGridPointObject*)type->tp_alloc(type, 0); + //auto target = std::static_pointer_cast(target); + obj->data = &(self->data->points[x + self->data->grid_x * y]); + obj->grid = self->data; + return (PyObject*)obj; +} + +PyMethodDef UIGrid::methods[] = { + {"at", (PyCFunction)UIGrid::py_at, METH_O}, + {NULL, NULL, 0, NULL} +}; + + +PyGetSetDef UIGrid::getsetters[] = { + + // TODO - refactor into get_vector_member with field identifier values `(void*)n` + {"grid_size", (getter)UIGrid::get_grid_size, NULL, "Grid dimensions (grid_x, grid_y)", NULL}, + {"position", (getter)UIGrid::get_position, (setter)UIGrid::set_position, "Position of the grid (x, y)", NULL}, + {"size", (getter)UIGrid::get_size, (setter)UIGrid::set_size, "Size of the grid (width, height)", NULL}, + {"center", (getter)UIGrid::get_center, (setter)UIGrid::set_center, "Grid coordinate at the center of the Grid's view (pan)", NULL}, + + {"entities", (getter)UIGrid::get_children, NULL, "EntityCollection of entities on this grid", NULL}, + + {"x", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "top-left corner X-coordinate", (void*)0}, + {"y", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "top-left corner Y-coordinate", (void*)1}, + {"w", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "visible widget width", (void*)2}, + {"h", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "visible widget height", (void*)3}, + {"center_x", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "center of the view X-coordinate", (void*)4}, + {"center_y", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "center of the view Y-coordinate", (void*)5}, + {"zoom", (getter)UIGrid::get_float_member, (setter)UIGrid::set_float_member, "zoom factor for displaying the Grid", (void*)6}, + + {"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 + {NULL} /* Sentinel */ +}; + +PyObject* UIGrid::get_children(PyUIGridObject* self, void* closure) +{ + // create PyUICollection instance pointing to self->data->children + //PyUIEntityCollectionObject* o = (PyUIEntityCollectionObject*)PyUIEntityCollectionType.tp_alloc(&PyUIEntityCollectionType, 0); + auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "EntityCollection"); + auto o = (PyUIEntityCollectionObject*)type->tp_alloc(type, 0); + if (o) { + o->data = self->data->entities; // todone. / BUGFIX - entities isn't a shared pointer on UIGrid, what to do? -- I made it a sp>> + o->grid = self->data; + } + return (PyObject*)o; +} + +/* // TODO standard pointer would need deleted, but I opted for a shared pointer. tp_dealloc currently not even defined in the PyTypeObject +void PyUIGrid_dealloc(PyUIGridObject* self) { + delete self->data; // Clean up the allocated UIGrid object + Py_TYPE(self)->tp_free((PyObject*)self); +} +*/ + +int UIEntityCollectionIter::init(PyUIEntityCollectionIterObject* self, PyObject* args, PyObject* kwds) +{ + PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); + return -1; +} + +PyObject* UIEntityCollectionIter::next(PyUIEntityCollectionIterObject* self) +{ + if (self->data->size() != self->start_size) + { + PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration"); + return NULL; + } + + if (self->index > self->start_size - 1) + { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + self->index++; + auto vec = self->data.get(); + if (!vec) + { + PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); + return NULL; + } + // Advance list iterator since Entities are not stored in a vector (if this code even worked) + // vectors only: //auto target = (*vec)[self->index-1]; + //auto l_front = (*vec).begin(); + //std::advance(l_front, self->index-1); + + // TODO build PyObject* of the correct UIDrawable subclass to return + //return py_instance(target); + return NULL; +} + +PyObject* UIEntityCollectionIter::repr(PyUIEntityCollectionIterObject* self) +{ + std::ostringstream ss; + if (!self->data) ss << ""; + else { + ss << "data->size() << " child objects, @ index " << self->index << ")>"; + } + std::string repr_str = ss.str(); + return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); +} + +Py_ssize_t UIEntityCollection::len(PyUIEntityCollectionObject* self) { + return self->data->size(); +} + +PyObject* UIEntityCollection::getitem(PyUIEntityCollectionObject* self, Py_ssize_t index) { + // build a Python version of item at self->data[index] + // Copy pasted:: + auto vec = self->data.get(); + if (!vec) + { + PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); + return NULL; + } + while (index < 0) index += self->data->size(); + if (index > self->data->size() - 1) + { + PyErr_SetString(PyExc_IndexError, "EntityCollection index out of range"); + return NULL; + } + auto l_begin = (*vec).begin(); + std::advance(l_begin, index); + auto target = *l_begin; //auto target = (*vec)[index]; + //RET_PY_INSTANCE(target); + // construct and return an entity object that points directly into the UIGrid's entity vector + //PyUIEntityObject* o = (PyUIEntityObject*)((&PyUIEntityType)->tp_alloc(&PyUIEntityType, 0)); + auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"); + auto o = (PyUIEntityObject*)type->tp_alloc(type, 0); + auto p = std::static_pointer_cast(target); + o->data = p; + return (PyObject*)o; +return NULL; + + +} + +PySequenceMethods UIEntityCollection::sqmethods = { + .sq_length = (lenfunc)UIEntityCollection::len, + .sq_item = (ssizeargfunc)UIEntityCollection::getitem, + //.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) +{ + // if not UIDrawable subclass, reject it + // self->data->push_back( c++ object inside o ); + + // this would be a great use case for .tp_base + //if (!PyObject_IsInstance(o, (PyObject*)&PyUIEntityType)) + if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) + { + PyErr_SetString(PyExc_TypeError, "Only Entity objects can be added to EntityCollection"); + return NULL; + } + PyUIEntityObject* entity = (PyUIEntityObject*)o; + self->data->push_back(entity->data); + entity->data->grid = self->grid; + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject* o) +{ + if (!PyLong_Check(o)) + { + PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove"); + return NULL; + } + long index = PyLong_AsLong(o); + 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 correct part of the list + self->data->erase(std::next(self->data->begin(), index)); + Py_INCREF(Py_None); + return Py_None; +} + +PyMethodDef UIEntityCollection::methods[] = { + {"append", (PyCFunction)UIEntityCollection::append, METH_O}, + //{"extend", (PyCFunction)UIEntityCollection::extend, METH_O}, // TODO + {"remove", (PyCFunction)UIEntityCollection::remove, METH_O}, + {NULL, NULL, 0, NULL} +}; + +PyObject* UIEntityCollection::repr(PyUIEntityCollectionObject* self) +{ + std::ostringstream ss; + if (!self->data) ss << ""; + else { + ss << "data->size() << " child objects)>"; + } + std::string repr_str = ss.str(); + return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); +} + +int UIEntityCollection::init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds) +{ + PyErr_SetString(PyExc_TypeError, "EntityCollection cannot be instantiated: a C++ data source is required."); + return -1; +} + +PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self) +{ + //PyUIEntityCollectionIterObject* iterObj; + //iterObj = (PyUIEntityCollectionIterObject*)PyUIEntityCollectionIterType.tp_alloc(&PyUIEntityCollectionIterType, 0); + auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "EntityCollectionIter"); + auto iterObj = (PyUIEntityCollectionIterObject*)type->tp_alloc(type, 0); + if (iterObj == NULL) { + return NULL; // Failed to allocate memory for the iterator object + } + + iterObj->data = self->data; + iterObj->index = 0; + iterObj->start_size = self->data->size(); + + return (PyObject*)iterObj; +} diff --git a/src/UIGrid.h b/src/UIGrid.h new file mode 100644 index 0000000..8804705 --- /dev/null +++ b/src/UIGrid.h @@ -0,0 +1,183 @@ +#pragma once +#include "Common.h" +#include "Python.h" +#include "structmember.h" +#include "IndexTexture.h" +#include "Resources.h" +#include + +#include "PyCallable.h" +#include "PyTexture.h" +#include "PyColor.h" +#include "PyVector.h" +#include "PyFont.h" + +#include "UIGridPoint.h" +#include "UIEntity.h" +#include "UIDrawable.h" +#include "UIBase.h" + +class UIGrid: public UIDrawable +{ +private: + std::shared_ptr ptex; +public: + UIGrid(); + //UIGrid(int, int, IndexTexture*, float, float, float, float); + UIGrid(int, int, std::shared_ptr, sf::Vector2f, sf::Vector2f); + void update(); + void render(sf::Vector2f) override final; + UIGridPoint& at(int, int); + PyObjectsEnum derived_type() override final; + //void setSprite(int); + virtual UIDrawable* click_at(sf::Vector2f point) override final; + + int grid_x, grid_y; + //int grid_size; // grid sizes are implied by IndexTexture now + sf::RectangleShape box; + float center_x, center_y, zoom; + //IndexTexture* itex; + std::shared_ptr getTexture(); + sf::Sprite sprite, output; + sf::RenderTexture renderTexture; + std::vector points; + std::shared_ptr>> entities; + + static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds); + static PyObject* get_grid_size(PyUIGridObject* self, void* closure); + static PyObject* get_position(PyUIGridObject* self, void* closure); + static int set_position(PyUIGridObject* self, PyObject* value, void* closure); + static PyObject* get_size(PyUIGridObject* self, void* closure); + static int set_size(PyUIGridObject* self, PyObject* value, void* closure); + static PyObject* get_center(PyUIGridObject* self, void* closure); + static int set_center(PyUIGridObject* self, PyObject* value, void* closure); + static PyObject* get_float_member(PyUIGridObject* self, void* closure); + static int set_float_member(PyUIGridObject* self, PyObject* value, void* closure); + static PyObject* get_texture(PyUIGridObject* self, void* closure); + static PyObject* py_at(PyUIGridObject* self, PyObject* o); + static PyMethodDef methods[]; + static PyGetSetDef getsetters[]; + static PyObject* get_children(PyUIGridObject* self, void* closure); + +}; + +typedef struct { + PyObject_HEAD + std::shared_ptr>> data; + std::shared_ptr grid; +} PyUIEntityCollectionObject; + +class UIEntityCollection { +public: + static PySequenceMethods sqmethods; + static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o); + static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o); + 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); +}; + +typedef struct { + PyObject_HEAD + std::shared_ptr>> data; + int index; + int start_size; +} PyUIEntityCollectionIterObject; + +class UIEntityCollectionIter { +public: + static int init(PyUIEntityCollectionIterObject* self, PyObject* args, PyObject* kwds); + static PyObject* next(PyUIEntityCollectionIterObject* self); + static PyObject* repr(PyUIEntityCollectionIterObject* self); + static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index); + +}; + +namespace mcrfpydef { + static PyTypeObject PyUIGridType = { + //PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.Grid", + .tp_basicsize = sizeof(PyUIGridObject), + .tp_itemsize = 0, + //.tp_dealloc = (destructor)[](PyObject* self) + //{ + // PyUIGridObject* obj = (PyUIGridObject*)self; + // obj->data.reset(); + // Py_TYPE(self)->tp_free(self); + //}, + //TODO - PyUIGrid REPR def: + // .tp_repr = (reprfunc)UIGrid::repr, + //.tp_hash = NULL, + //.tp_iter + //.tp_iternext + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = PyDoc_STR("docstring"), + .tp_methods = UIGrid::methods, + //.tp_members = UIGrid::members, + .tp_getset = UIGrid::getsetters, + //.tp_base = NULL, + .tp_init = (initproc)UIGrid::init, + .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* + { + PyUIGridObject* self = (PyUIGridObject*)type->tp_alloc(type, 0); + if (self) self->data = std::make_shared(); + return (PyObject*)self; + } + }; + + static PyTypeObject PyUIEntityCollectionIterType = { + //PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.UICollectionIter", + .tp_basicsize = sizeof(PyUIEntityCollectionIterObject), + .tp_itemsize = 0, + .tp_dealloc = (destructor)[](PyObject* self) + { + PyUIEntityCollectionIterObject* obj = (PyUIEntityCollectionIterObject*)self; + obj->data.reset(); + Py_TYPE(self)->tp_free(self); + }, + .tp_repr = (reprfunc)UIEntityCollectionIter::repr, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = PyDoc_STR("Iterator for a collection of UI objects"), + .tp_iternext = (iternextfunc)UIEntityCollectionIter::next, + //.tp_getset = UIEntityCollection::getset, + .tp_init = (initproc)UIEntityCollectionIter::init, // just raise an exception + .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* + { + PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required."); + return NULL; + } + }; + + static PyTypeObject PyUIEntityCollectionType = { + //PyVarObject_/HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.EntityCollection", + .tp_basicsize = sizeof(PyUIEntityCollectionObject), + .tp_itemsize = 0, + .tp_dealloc = (destructor)[](PyObject* self) + { + PyUIEntityCollectionObject* obj = (PyUIEntityCollectionObject*)self; + obj->data.reset(); + Py_TYPE(self)->tp_free(self); + }, + .tp_repr = (reprfunc)UIEntityCollection::repr, + .tp_as_sequence = &UIEntityCollection::sqmethods, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"), + .tp_iter = (getiterfunc)UIEntityCollection::iter, + .tp_methods = UIEntityCollection::methods, // append, remove + //.tp_getset = UIEntityCollection::getset, + .tp_init = (initproc)UIEntityCollection::init, // just raise an exception + .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* + { + // Does PyUIEntityCollectionType need __new__ if it's not supposed to be instantiable by the user? + // Should I just raise an exception? Or is the uninitialized shared_ptr enough of a blocker? + PyErr_SetString(PyExc_TypeError, "EntityCollection cannot be instantiated: a C++ data source is required."); + return NULL; + } + }; + +} diff --git a/src/UIGridPoint.cpp b/src/UIGridPoint.cpp new file mode 100644 index 0000000..b30d493 --- /dev/null +++ b/src/UIGridPoint.cpp @@ -0,0 +1,134 @@ +#include "UIGridPoint.h" + +UIGridPoint::UIGridPoint() +: color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false), + tilesprite(-1), tile_overlay(-1), uisprite(-1) +{} + +// Utility function to convert sf::Color to PyObject* +PyObject* sfColor_to_PyObject(sf::Color color) { + return Py_BuildValue("(iiii)", color.r, color.g, color.b, color.a); +} + +// Utility function to convert PyObject* to sf::Color +sf::Color PyObject_to_sfColor(PyObject* obj) { + int r, g, b, a = 255; // Default alpha to fully opaque if not specified + if (!PyArg_ParseTuple(obj, "iii|i", &r, &g, &b, &a)) { + return sf::Color(); // Return default color on parse error + } + return sf::Color(r, g, b, a); +} + +PyObject* UIGridPoint::get_color(PyUIGridPointObject* self, void* closure) { + if (reinterpret_cast(closure) == 0) { // color + return sfColor_to_PyObject(self->data->color); + } else { // color_overlay + return sfColor_to_PyObject(self->data->color_overlay); + } +} + +int UIGridPoint::set_color(PyUIGridPointObject* self, PyObject* value, void* closure) { + sf::Color color = PyObject_to_sfColor(value); + if (reinterpret_cast(closure) == 0) { // color + self->data->color = color; + } else { // color_overlay + self->data->color_overlay = color; + } + return 0; +} + +PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure) { + if (reinterpret_cast(closure) == 0) { // walkable + return PyBool_FromLong(self->data->walkable); + } else { // transparent + return PyBool_FromLong(self->data->transparent); + } +} + +int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure) { + if (value == Py_True) { + if (reinterpret_cast(closure) == 0) { // walkable + self->data->walkable = true; + } else { // transparent + self->data->transparent = true; + } + } else if (value == Py_False) { + if (reinterpret_cast(closure) == 0) { // walkable + self->data->walkable = false; + } else { // transparent + self->data->transparent = false; + } + } else { + PyErr_SetString(PyExc_ValueError, "Expected a boolean value"); + return -1; + } + return 0; +} + +PyObject* UIGridPoint::get_int_member(PyUIGridPointObject* self, void* closure) { + switch(reinterpret_cast(closure)) { + case 0: return PyLong_FromLong(self->data->tilesprite); + case 1: return PyLong_FromLong(self->data->tile_overlay); + case 2: return PyLong_FromLong(self->data->uisprite); + default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return nullptr; + } +} + +int UIGridPoint::set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure) { + long val = PyLong_AsLong(value); + if (PyErr_Occurred()) return -1; + + switch(reinterpret_cast(closure)) { + case 0: self->data->tilesprite = val; break; + case 1: self->data->tile_overlay = val; break; + case 2: self->data->uisprite = val; break; + default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return -1; + } + return 0; +} + +PyGetSetDef UIGridPoint::getsetters[] = { + {"color", (getter)UIGridPoint::get_color, (setter)UIGridPoint::set_color, "GridPoint color", (void*)0}, + {"color_overlay", (getter)UIGridPoint::get_color, (setter)UIGridPoint::set_color, "GridPoint color overlay", (void*)1}, + {"walkable", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint walkable", (void*)0}, + {"transparent", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint transparent", (void*)1}, + {"tilesprite", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "Tile sprite index", (void*)0}, + {"tile_overlay", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "Tile overlay sprite index", (void*)1}, + {"uisprite", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "UI sprite index", (void*)2}, + {NULL} /* Sentinel */ +}; + +PyObject* UIGridPointState::get_bool_member(PyUIGridPointStateObject* self, void* closure) { + if (reinterpret_cast(closure) == 0) { // visible + return PyBool_FromLong(self->data->visible); + } else { // discovered + return PyBool_FromLong(self->data->discovered); + } +} + +int UIGridPointState::set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure) { + if (!PyBool_Check(value)) { + PyErr_SetString(PyExc_TypeError, "Value must be a boolean"); + return -1; + } + + int truthValue = PyObject_IsTrue(value); + if (truthValue < 0) { + return -1; // PyObject_IsTrue returns -1 on error + } + + if (reinterpret_cast(closure) == 0) { // visible + self->data->visible = truthValue; + } else { // discovered + self->data->discovered = truthValue; + } + + return 0; +} + +PyGetSetDef UIGridPointState::getsetters[] = { + {"visible", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Is the GridPointState visible", (void*)0}, + {"discovered", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Has the GridPointState been discovered", (void*)1}, + {NULL} /* Sentinel */ +}; + diff --git a/src/UIGridPoint.h b/src/UIGridPoint.h new file mode 100644 index 0000000..8161df8 --- /dev/null +++ b/src/UIGridPoint.h @@ -0,0 +1,90 @@ +#pragma once +#include "Common.h" +#include "Python.h" +#include "structmember.h" +#include "IndexTexture.h" +#include "Resources.h" +#include + +#include "PyCallable.h" +#include "PyTexture.h" +#include "PyColor.h" +#include "PyVector.h" +#include "PyFont.h" + +static PyObject* sfColor_to_PyObject(sf::Color color); +static sf::Color PyObject_to_sfColor(PyObject* obj); + +class UIGrid; +class UIEntity; +class UIGridPoint; +class UIGridPointState; + +typedef struct { + PyObject_HEAD + UIGridPoint* data; + std::shared_ptr grid; +} PyUIGridPointObject; + +typedef struct { + PyObject_HEAD + UIGridPointState* data; + std::shared_ptr grid; + std::shared_ptr entity; +} PyUIGridPointStateObject; + +// UIGridPoint - revised grid data for each point +class UIGridPoint +{ +public: + sf::Color color, color_overlay; + bool walkable, transparent; + int tilesprite, tile_overlay, uisprite; + UIGridPoint(); + + static int set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure); + static PyGetSetDef getsetters[]; + static PyObject* get_color(PyUIGridPointObject* self, void* closure); + static PyObject* get_int_member(PyUIGridPointObject* self, void* closure); + static int set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure); + static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure); + static int set_color(PyUIGridPointObject* self, PyObject* value, void* closure); +}; + +// UIGridPointState - entity-specific info for each cell +class UIGridPointState +{ +public: + bool visible, discovered; + + static PyObject* get_bool_member(PyUIGridPointStateObject* self, void* closure); + static int set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure); + static PyGetSetDef getsetters[]; +}; + +namespace mcrfpydef { + static PyTypeObject PyUIGridPointType = { + //PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.GridPoint", + .tp_basicsize = sizeof(PyUIGridPointObject), + .tp_itemsize = 0, + // Methods omitted for brevity + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "UIGridPoint object", + .tp_getset = UIGridPoint::getsetters, + //.tp_init = (initproc)PyUIGridPoint_init, // TODO Define the init function + .tp_new = PyType_GenericNew, + }; + + static PyTypeObject PyUIGridPointStateType = { + //PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.GridPointState", + .tp_basicsize = sizeof(PyUIGridPointStateObject), + .tp_itemsize = 0, + // Methods omitted for brevity + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "UIGridPointState object", // TODO: Add PyUIGridPointState tp_init + .tp_getset = UIGridPointState::getsetters, + .tp_new = PyType_GenericNew, + }; +} diff --git a/src/UISprite.cpp b/src/UISprite.cpp new file mode 100644 index 0000000..1f3a214 --- /dev/null +++ b/src/UISprite.cpp @@ -0,0 +1,215 @@ +#include "UISprite.h" +#include "GameEngine.h" + +UIDrawable* UISprite::click_at(sf::Vector2f point) +{ + if (click_callable) + { + if(sprite.getGlobalBounds().contains(point)) return this; + } + return NULL; +} + +UISprite::UISprite() {} + +UISprite::UISprite(std::shared_ptr _ptex, int _sprite_index, sf::Vector2f _pos, float _scale) +: ptex(_ptex), sprite_index(_sprite_index) +{ + sprite = ptex->sprite(sprite_index, _pos, sf::Vector2f(_scale, _scale)); +} + +void UISprite::render(sf::Vector2f offset) +{ + sprite.move(offset); + Resources::game->getWindow().draw(sprite); + sprite.move(-offset); +} + +void UISprite::render(sf::Vector2f offset, sf::RenderTexture& target) +{ + sprite.move(offset); + target.draw(sprite); + sprite.move(-offset); +} + +void UISprite::setPosition(sf::Vector2f pos) +{ + sprite.setPosition(pos); +} + +void UISprite::setScale(sf::Vector2f s) +{ + sprite.setScale(s); +} + +void UISprite::setTexture(std::shared_ptr _ptex, int _sprite_index) +{ + ptex = _ptex; + if (_sprite_index != -1) // if you are changing textures, there's a good chance you need a new index too + sprite_index = _sprite_index; + sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale()); +} + +void UISprite::setSpriteIndex(int _sprite_index) +{ + sprite_index = _sprite_index; + sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale()); +} + +sf::Vector2f UISprite::getScale() +{ + return sprite.getScale(); +} + +sf::Vector2f UISprite::getPosition() +{ + return sprite.getPosition(); +} + +std::shared_ptr UISprite::getTexture() +{ + return ptex; +} + +int UISprite::getSpriteIndex() +{ + return sprite_index; +} + +PyObjectsEnum UISprite::derived_type() +{ + return PyObjectsEnum::UISPRITE; +} + +PyObject* UISprite::get_float_member(PyUISpriteObject* self, void* closure) +{ + auto member_ptr = reinterpret_cast(closure); + if (member_ptr == 0) + return PyFloat_FromDouble(self->data->getPosition().x); + else if (member_ptr == 1) + return PyFloat_FromDouble(self->data->getPosition().y); + else if (member_ptr == 2) + return PyFloat_FromDouble(self->data->getScale().x); // scale X and Y are identical, presently + else + { + PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); + return nullptr; + } +} + +int UISprite::set_float_member(PyUISpriteObject* self, PyObject* value, void* closure) +{ + float val; + auto member_ptr = reinterpret_cast(closure); + if (PyFloat_Check(value)) + { + val = PyFloat_AsDouble(value); + } + else if (PyLong_Check(value)) + { + val = PyLong_AsLong(value); + } + else + { + PyErr_SetString(PyExc_TypeError, "Value must be a floating point number."); + return -1; + } + if (member_ptr == 0) //x + self->data->setPosition(sf::Vector2f(val, self->data->getPosition().y)); + else if (member_ptr == 1) //y + self->data->setPosition(sf::Vector2f(self->data->getPosition().x, val)); + else if (member_ptr == 2) // scale + self->data->setScale(sf::Vector2f(val, val)); + return 0; +} + +PyObject* UISprite::get_int_member(PyUISpriteObject* self, void* closure) +{ + auto member_ptr = reinterpret_cast(closure); + if (true) {} + else + { + PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); + return nullptr; + } + + return PyLong_FromDouble(self->data->getSpriteIndex()); +} + +int UISprite::set_int_member(PyUISpriteObject* self, PyObject* value, void* closure) +{ + int val; + auto member_ptr = reinterpret_cast(closure); + if (PyLong_Check(value)) + { + val = PyLong_AsLong(value); + } + else + { + PyErr_SetString(PyExc_TypeError, "Value must be an integer."); + return -1; + } + self->data->setSpriteIndex(val); + return 0; +} + +PyObject* UISprite::get_texture(PyUISpriteObject* self, void* closure) +{ + return self->data->getTexture()->pyObject(); +} + +int UISprite::set_texture(PyUISpriteObject* self, PyObject* value, void* closure) +{ + return -1; +} + +PyGetSetDef UISprite::getsetters[] = { + {"x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "X coordinate of top-left corner", (void*)0}, + {"y", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Y coordinate of top-left corner", (void*)1}, + {"scale", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Size factor", (void*)2}, + {"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}, + {NULL} +}; + +PyObject* UISprite::repr(PyUISpriteObject* self) +{ + std::ostringstream ss; + if (!self->data) ss << ""; + else { + //auto sprite = self->data->sprite; + ss << ""; + } + std::string repr_str = ss.str(); + return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); +} + +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; + PyObject* texture; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif", + const_cast(keywords), &x, &y, &texture, &sprite_index, &scale)) + { + return -1; + } + + // 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; + } + 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; +} diff --git a/src/UISprite.h b/src/UISprite.h new file mode 100644 index 0000000..917eec5 --- /dev/null +++ b/src/UISprite.h @@ -0,0 +1,90 @@ +#pragma once +#include "Common.h" +#include "Python.h" +#include "structmember.h" +#include "IndexTexture.h" +#include "Resources.h" +#include + +#include "PyCallable.h" +#include "PyTexture.h" +#include "PyColor.h" +#include "PyVector.h" +#include "PyFont.h" +#include "UIDrawable.h" +#include "UIBase.h" + +class UISprite: public UIDrawable +{ +private: + int sprite_index; + sf::Sprite sprite; +protected: + std::shared_ptr ptex; +public: + UISprite(); + UISprite(std::shared_ptr, int, sf::Vector2f, float); + void update(); + void render(sf::Vector2f) override final; + virtual UIDrawable* click_at(sf::Vector2f point) override final; + + void render(sf::Vector2f, sf::RenderTexture&); + + void setPosition(sf::Vector2f); + sf::Vector2f getPosition(); + void setScale(sf::Vector2f); + sf::Vector2f getScale(); + void setSpriteIndex(int); + int getSpriteIndex(); + + void setTexture(std::shared_ptr _ptex, int _sprite_index=-1); + std::shared_ptr getTexture(); + + PyObjectsEnum derived_type() override final; + + + static PyObject* get_float_member(PyUISpriteObject* self, void* closure); + static int set_float_member(PyUISpriteObject* self, PyObject* value, void* closure); + static PyObject* get_int_member(PyUISpriteObject* self, void* closure); + static int set_int_member(PyUISpriteObject* self, PyObject* value, void* closure); + static PyObject* get_texture(PyUISpriteObject* self, void* closure); + static int set_texture(PyUISpriteObject* self, PyObject* value, void* closure); + static PyGetSetDef getsetters[]; + static PyObject* repr(PyUISpriteObject* self); + static int init(PyUISpriteObject* self, PyObject* args, PyObject* kwds); + +}; + +namespace mcrfpydef { + static PyTypeObject PyUISpriteType = { + //PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "mcrfpy.Sprite", + .tp_basicsize = sizeof(PyUISpriteObject), + .tp_itemsize = 0, + .tp_dealloc = (destructor)[](PyObject* self) + { + PyUISpriteObject* obj = (PyUISpriteObject*)self; + // release reference to font object + //if (obj->texture) Py_DECREF(obj->texture); + obj->data.reset(); + Py_TYPE(self)->tp_free(self); + }, + .tp_repr = (reprfunc)UISprite::repr, + //.tp_hash = NULL, + //.tp_iter + //.tp_iternext + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = PyDoc_STR("docstring"), + //.tp_methods = PyUIFrame_methods, + //.tp_members = PyUIFrame_members, + .tp_getset = UISprite::getsetters, + //.tp_base = NULL, + .tp_init = (initproc)UISprite::init, + .tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject* + { + PyUISpriteObject* self = (PyUISpriteObject*)type->tp_alloc(type, 0); + //if (self) self->data = std::make_shared(); + return (PyObject*)self; + } + }; +}