replace attributes with an augmented user object system

This commit is contained in:
HJfod 2024-02-22 23:39:21 +02:00
parent e452f482a8
commit f9d7cfdc68
4 changed files with 55 additions and 65 deletions

View file

@ -838,19 +838,31 @@ public:
* and the previous UserObject (if existed) will be relese.
* The UserObject will be released in CCNode's destructure.
*
* @note In Geode, this actually sets the user object with the ID ""
* (empty string)
*
* @param A user assigned CCObject
*/
virtual void setUserObject(CCObject *pUserObject);
/**
* Set a user-assigned CCObject with a specific ID. This allows nodes to
* have multiple user objects. Objects should be prefixed with the mod ID.
* Assigning a null removes the user object with the ID
*/
GEODE_DLL void setUserObject(std::string const& id, CCObject* object);
/**
* Get a user-assigned CCObject with the specific ID
*/
GEODE_DLL CCObject* getUserObject(std::string const& id);
/// @} end of Tag & User Data
private:
friend class geode::modifier::FieldContainer;
GEODE_DLL geode::modifier::FieldContainer* getFieldContainer();
#ifndef GEODE_IS_MEMBER_TEST
GEODE_DLL std::optional<matjson::Value> getAttributeInternal(std::string const& attribute);
#endif
GEODE_DLL void addEventListenerInternal(
std::string const& id,
geode::EventListenerProtocol* protocol
@ -928,38 +940,6 @@ public:
*/
GEODE_DLL bool hasAncestor(CCNode* ancestor);
#ifndef GEODE_IS_MEMBER_TEST
/**
* Set an attribute on a node. Attributes are a system added by Geode,
* where a node may have any sort of extra data associated with it. Used
* for mod intercommunication. For example, a mod that adds scrollbars to
* layers might check if the layer has an attribute set for whether the
* scrollbar should be disabled. The key of the attribute should be
* prefixed with the mod ID, like hjfod.cool-scrollbars/enable.
* @param attribute The attribute key. Should be prefixed with the mod ID,
* like hjfod.cool-scrollbars/enable
* @param value The value of the attribute
* @note Geode addition
*/
GEODE_DLL void setAttribute(std::string const& attribute, matjson::Value const& value);
/**
* Get an attribute from the node. Attributes may be anything
* @param attribute The attribute key
* @returns The value, or nullopt if the attribute doesn't exist or if the
* type didn't match
* @note Geode addition
*/
template<class T>
std::optional<T> getAttribute(std::string const& attribute) {
if (auto value = this->getAttributeInternal(attribute)) {
if (value.value().template is<T>()) {
return value.value().template as<T>();
}
}
return std::nullopt;
}
#endif
/**
* Set the Layout for this node. Used to automatically position children,
* based on the selected layout. In order to apply the layout after a child
@ -1834,23 +1814,23 @@ NS_CC_END
#ifndef GEODE_IS_MEMBER_TEST
namespace geode {
struct GEODE_DLL AttributeSetEvent : public Event {
struct GEODE_DLL UserObjectSetEvent : public Event {
cocos2d::CCNode* node;
const std::string id;
matjson::Value& value;
cocos2d::CCObject* value;
AttributeSetEvent(cocos2d::CCNode* node, std::string const& id, matjson::Value& value);
UserObjectSetEvent(cocos2d::CCNode* node, std::string const& id, cocos2d::CCObject* value);
};
class GEODE_DLL AttributeSetFilter : public EventFilter<AttributeSetEvent> {
class GEODE_DLL AttributeSetFilter : public EventFilter<UserObjectSetEvent> {
public:
using Callback = void(AttributeSetEvent*);
using Callback = void(UserObjectSetEvent*);
protected:
std::string m_targetID;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, AttributeSetEvent* event);
ListenerResult handle(utils::MiniFunction<Callback> fn, UserObjectSetEvent* event);
AttributeSetFilter(std::string const& id);
};

View file

@ -17,11 +17,10 @@ struct ProxyCCNode;
class GeodeNodeMetadata final : public cocos2d::CCObject {
private:
FieldContainer* m_fieldContainer;
Ref<cocos2d::CCObject> m_userObject;
std::string m_id = "";
Ref<Layout> m_layout = nullptr;
Ref<LayoutOptions> m_layoutOptions = nullptr;
std::unordered_map<std::string, matjson::Value> m_attributes;
std::unordered_map<std::string, Ref<CCObject>> m_userObjects;
std::unordered_set<std::unique_ptr<EventListenerProtocol>> m_eventListeners;
std::unordered_map<std::string, std::unique_ptr<EventListenerProtocol>> m_idEventListeners;
@ -53,8 +52,8 @@ public:
meta->retain();
if (old) {
meta->m_userObject = old;
// the old user object has been retained by CCNode
meta->m_userObjects.insert({ "", old });
// the old user object is now managed by Ref
old->release();
}
return meta;
@ -69,18 +68,24 @@ public:
#include <Geode/modify/CCNode.hpp>
struct ProxyCCNode : Modify<ProxyCCNode, CCNode> {
virtual CCObject* getUserObject() {
if (typeinfo_cast<CCNode*>(this)) {
return GeodeNodeMetadata::set(this)->m_userObject;
if (auto asNode = typeinfo_cast<CCNode*>(this)) {
return asNode->getUserObject("");
}
else {
// apparently this function is the same as
// CCDirector::getNextScene so yeah
return m_pUserObject;
}
virtual void setUserObject(CCObject* obj) {
if (typeinfo_cast<CCNode*>(this)) {
GeodeNodeMetadata::set(this)->m_userObject = obj;
}
virtual void setUserObject(CCObject* obj) {
if (auto asNode = typeinfo_cast<CCNode*>(this)) {
asNode->setUserObject("", obj);
}
else {
CC_SAFE_RELEASE(m_pUserObject);
m_pUserObject = obj;
CC_SAFE_RETAIN(m_pUserObject);
}
}
};
@ -166,10 +171,10 @@ void CCNode::updateLayout(bool updateChildOrder) {
}
}
AttributeSetEvent::AttributeSetEvent(CCNode* node, std::string const& id, matjson::Value& value)
UserObjectSetEvent::UserObjectSetEvent(CCNode* node, std::string const& id, CCObject* value)
: node(node), id(id), value(value) {}
ListenerResult AttributeSetFilter::handle(MiniFunction<Callback> fn, AttributeSetEvent* event) {
ListenerResult AttributeSetFilter::handle(MiniFunction<Callback> fn, UserObjectSetEvent* event) {
if (event->id == m_targetID) {
fn(event);
}
@ -178,18 +183,23 @@ ListenerResult AttributeSetFilter::handle(MiniFunction<Callback> fn, AttributeSe
AttributeSetFilter::AttributeSetFilter(std::string const& id) : m_targetID(id) {}
void CCNode::setAttribute(std::string const& attr, matjson::Value const& value) {
void CCNode::setUserObject(std::string const& id, CCObject* value) {
auto meta = GeodeNodeMetadata::set(this);
meta->m_attributes[attr] = value;
AttributeSetEvent(this, attr, meta->m_attributes.at(attr)).post();
if (value) {
meta->m_userObjects[id] = value;
}
else {
meta->m_userObjects.erase(id);
}
UserObjectSetEvent(this, id, value).post();
}
std::optional<matjson::Value> CCNode::getAttributeInternal(std::string const& attr) {
CCObject* CCNode::getUserObject(std::string const& id) {
auto meta = GeodeNodeMetadata::set(this);
if (meta->m_attributes.count(attr)) {
return meta->m_attributes.at(attr);
if (meta->m_userObjects.count(id)) {
return meta->m_userObjects.at(id);
}
return std::nullopt;
return nullptr;
}
void CCNode::addEventListenerInternal(std::string const& id, EventListenerProtocol* listener) {

View file

@ -56,7 +56,7 @@ bool InputNode::init(
m_input->setMaxLabelScale(.85f);
m_input->setMaxLabelLength(maxCharCount);
m_input->setPosition(width / 2, height / 2);
m_input->setAttribute("fix-text-input", true);
m_input->setUserObject("fix-text-input", CCBool::create(true));
if (filter.length()) {
m_input->setAllowedChars(filter);
}

View file

@ -9,7 +9,7 @@ struct TextInputNodeFix : Modify<TextInputNodeFix, CCTextInputNode> {
GEODE_FORWARD_COMPAT_DISABLE_HOOKS("TextInputNode fix")
bool ccTouchBegan(cocos2d::CCTouch* touch, cocos2d::CCEvent* event) {
if (!this->template getAttribute<bool>("fix-text-input").value_or(false)) {
if (!this->getUserObject("fix-text-input")) {
return CCTextInputNode::ccTouchBegan(touch, event);
}
@ -73,7 +73,7 @@ bool TextInput::init(float width, std::string const& placeholder, std::string co
m_input->setLabelPlaceholderColor({ 150, 150, 150 });
m_input->setLabelPlaceholderScale(.6f);
m_input->setMaxLabelScale(.6f);
m_input->setAttribute("fix-text-input", true);
m_input->setUserObject("fix-text-input", CCBool::create(true));
this->addChildAtPosition(m_input, cocos2d::Anchor::Center);
return true;