Skip to content

Commit 30618e8

Browse files
committed
merge from main
2 parents 1312b62 + 745bc8a commit 30618e8

File tree

10 files changed

+422
-17
lines changed

10 files changed

+422
-17
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ add_subdirectory($ENV{GEODE_SDK} ${CMAKE_CURRENT_BINARY_DIR}/geode)
3131

3232
CPMAddPackage("gh:ocornut/[email protected]")
3333

34-
target_include_directories(${PROJECT_NAME} PRIVATE ${imgui_SOURCE_DIR})
34+
target_include_directories(${PROJECT_NAME} PRIVATE ${imgui_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include)
3535

3636
target_sources(${PROJECT_NAME} PRIVATE
3737
${imgui_SOURCE_DIR}/imgui.cpp

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,57 @@ Press `F11` (`F10` for MacOS) to open up the dev tools.
1010
* Inspect individual nodes
1111
* Easily change attributes like the position and color of nodes
1212
* Multiple built-in themes
13+
14+
## API
15+
16+
Developers can register their own node's properties to show up in DevTools like so:
17+
18+
```cpp
19+
#include <geode.devtools/include/API.hpp>
20+
21+
class MySprite : public CCSprite {
22+
public:
23+
enum class RandomEnum {
24+
Option1,
25+
Option2,
26+
Option3
27+
};
28+
29+
static void registerDevTools() {
30+
//
31+
devtools::registerNode<MySprite>([](MySprite* node) {
32+
devtools::label("My Sprite");
33+
devtools::property("Some Flag", node->m_someFlag);
34+
devtools::property("Some Float", node->m_someFloat);
35+
devtools::property("Some Int", node->m_someInt);
36+
devtools::property("Some String", node->m_someString);
37+
devtools::property("Some Color", node->m_someColor);
38+
devtools::enumerable("Some Enum", node->m_someEnum, {
39+
{ RandomEnum::Option1, "Option 1" },
40+
{ RandomEnum::Option2, "Option 2" },
41+
{ RandomEnum::Option3, "Option 3" }
42+
});
43+
devtools::button("Shake It", [&]{
44+
node->runAction(CCShaky3D::create(
45+
0.5f, CCSize(10, 10), 5, false
46+
));
47+
});
48+
});
49+
}
50+
51+
private:
52+
float m_someFloat = 3.14f;
53+
int m_someInt = 42;
54+
std::string m_someString = "text";
55+
ccColor3B m_someColor = {255, 0, 0};
56+
RandomEnum m_someEnum = RandomEnum::Option1;
57+
bool m_someFlag = true;
58+
};
59+
60+
$on_mod(Loaded) {
61+
// makes sure DevTools is loaded before registering
62+
devtools::waitForDevTools([] {
63+
MySprite::registerDevTools();
64+
});
65+
}
66+
```

about.md

Lines changed: 0 additions & 12 deletions
This file was deleted.

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## v1.10.0
2+
* Add node properties api (#63)
3+
* Fix a crash when removing layouts and layout options (#58)
4+
* iOS support (#59)
5+
16
## v1.9.0 && v1.9.1
27
* Bump to Geode `v4.4.0`
38
* Add support for `SimpleAxisLayout`

include/API.hpp

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#pragma once
2+
#include <Geode/loader/Event.hpp>
3+
#include <Geode/loader/Mod.hpp>
4+
#include <Geode/loader/ModEvent.hpp>
5+
#include <cocos2d.h>
6+
#include <functional>
7+
#include <initializer_list>
8+
#include <string>
9+
#include <type_traits>
10+
11+
namespace devtools {
12+
template <typename T>
13+
concept IsCCNode = std::is_base_of_v<cocos2d::CCNode, std::remove_pointer_t<T>>;
14+
15+
template <typename T>
16+
concept SupportedProperty = std::is_arithmetic_v<T> ||
17+
std::is_same_v<T, std::string> ||
18+
std::is_same_v<T, cocos2d::ccColor3B> ||
19+
std::is_same_v<T, cocos2d::ccColor4B> ||
20+
std::is_same_v<T, cocos2d::ccColor4F> ||
21+
std::is_same_v<T, cocos2d::CCPoint> ||
22+
std::is_same_v<T, cocos2d::CCSize> ||
23+
std::is_same_v<T, cocos2d::CCRect>;
24+
25+
struct RegisterNodeEvent final : geode::Event {
26+
RegisterNodeEvent(std::function<void(cocos2d::CCNode*)>&& callback)
27+
: callback(std::move(callback)) {}
28+
std::function<void(cocos2d::CCNode*)> callback;
29+
};
30+
31+
template <typename T>
32+
struct PropertyFnEvent final : geode::Event {
33+
PropertyFnEvent() {}
34+
using Fn = bool(const char* name, T&);
35+
Fn* fn = nullptr;
36+
};
37+
38+
struct DrawLabelFnEvent final : geode::Event {
39+
DrawLabelFnEvent() {}
40+
using Fn = void(const char* text);
41+
Fn* fn = nullptr;
42+
};
43+
44+
template <typename T>
45+
struct EnumerableFnEvent final : geode::Event {
46+
EnumerableFnEvent() {}
47+
using Fn = bool(const char* label, T* value, std::span<std::pair<T, const char*> const> items);
48+
Fn* fn = nullptr;
49+
};
50+
51+
struct ButtonFnEvent final : geode::Event {
52+
ButtonFnEvent() {}
53+
using Fn = bool(const char* label);
54+
Fn* fn = nullptr;
55+
};
56+
57+
/// @brief Checks if DevTools is currently loaded.
58+
/// @return True if DevTools is loaded, false otherwise.
59+
inline bool isLoaded() {
60+
return geode::Loader::get()->getLoadedMod("geode.devtools") != nullptr;
61+
}
62+
63+
/// @brief Waits for DevTools to be loaded and then calls the provided callback.
64+
/// @param callback The function to call once DevTools is loaded.
65+
template <typename F>
66+
void waitForDevTools(F&& callback) {
67+
if (isLoaded()) {
68+
callback();
69+
} else {
70+
auto devtools = geode::Loader::get()->getInstalledMod("geode.devtools");
71+
if (!devtools || !devtools->isEnabled()) return;
72+
73+
new geode::EventListener(
74+
[callback = std::forward<F>(callback)](geode::ModStateEvent*) {
75+
callback();
76+
},
77+
geode::ModStateFilter(devtools, geode::ModEventType::Loaded)
78+
);
79+
}
80+
}
81+
82+
/// @brief Registers a callback that will be called whenever a node of type T is opened in Attributes tab.
83+
/// @param callback The function to call with the node when it is opened.
84+
/// @see `devtools::property`, `devtools::label`, `devtools::enumerable`, `devtools::button`
85+
template <typename T, std::invocable<std::remove_pointer_t<T>*> F> requires IsCCNode<T>
86+
void registerNode(F&& callback) {
87+
RegisterNodeEvent([callback = std::forward<F>(callback)](cocos2d::CCNode* node) {
88+
if (auto casted = geode::cast::typeinfo_cast<std::remove_pointer_t<T>*>(node)) {
89+
callback(casted);
90+
}
91+
}).post();
92+
}
93+
94+
/// @brief Renders a property editor for the given value in the DevTools UI.
95+
/// @param name The name of the property to display.
96+
/// @param prop The property value to edit.
97+
/// @return True if the property was changed, false otherwise.
98+
/// @warning This function should only ever be called from within a registered node callback.
99+
template <typename T> requires SupportedProperty<T>
100+
bool property(const char* name, T& prop) {
101+
static auto fn = ([] {
102+
PropertyFnEvent<T> event;
103+
event.post();
104+
return event.fn;
105+
})();
106+
return fn ? fn(name, prop) : false;
107+
}
108+
109+
/// @brief Renders a label in the DevTools UI.
110+
/// @param text The text to display in the label.
111+
/// @warning This function should only ever be called from within a registered node callback.
112+
inline void label(const char* text) {
113+
static auto fn = ([] {
114+
DrawLabelFnEvent event;
115+
event.post();
116+
return event.fn;
117+
})();
118+
if (fn) fn(text);
119+
}
120+
121+
/// @brief Renders an enumerable property editor using radio buttons for the given value in the DevTools UI.
122+
/// @param label The label for the enumerable property.
123+
/// @param value The value to edit, which should be an enum or integral type.
124+
/// @param items A list of pairs where each pair contains a value and its corresponding label.
125+
/// @return True if the value was changed, false otherwise.
126+
/// @warning This function should only ever be called from within a registered node callback.
127+
template <typename T> requires std::is_integral_v<std::underlying_type_t<T>>
128+
bool enumerable(const char* label, T& value, std::initializer_list<std::pair<T, const char*>> items) {
129+
using ValueType = std::underlying_type_t<T>;
130+
static auto fn = ([] {
131+
EnumerableFnEvent<ValueType> event;
132+
event.post();
133+
return event.fn;
134+
})();
135+
return fn ? fn(
136+
label,
137+
reinterpret_cast<ValueType*>(&value),
138+
std::span(
139+
reinterpret_cast<std::pair<ValueType, const char*> const*>(&*items.begin()),
140+
reinterpret_cast<std::pair<ValueType, const char*> const*>(&*items.end())
141+
)
142+
) : false;
143+
}
144+
145+
/// @brief Renders a button in the DevTools UI.
146+
/// @param label The label for the button.
147+
/// @return True if the button was clicked, false otherwise.
148+
/// @warning This function should only ever be called from within a registered node callback.
149+
inline bool button(const char* label) {
150+
static auto fn = ([] {
151+
ButtonFnEvent event;
152+
event.post();
153+
return event.fn;
154+
})();
155+
return fn ? fn(label) : false;
156+
}
157+
158+
/// @brief Renders a button in the DevTools UI and calls the provided callback if the button is clicked.
159+
/// @param label The label for the button.
160+
/// @param callback The function to call when the button is clicked.
161+
/// @warning This function should only ever be called from within a registered node callback.
162+
template <typename F>
163+
void button(const char* label, F&& callback) {
164+
if (button(label)) {
165+
callback();
166+
}
167+
}
168+
}

mod.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"geode": "4.8.0",
3-
"version": "v1.9.1",
3+
"version": "v1.10.0",
44
"gd": {
5-
"win": "*",
5+
"win": "2.2074",
66
"android": "*",
77
"mac": "2.2074",
88
"ios": "2.2074"
@@ -11,6 +11,7 @@
1111
"name": "DevTools",
1212
"developer": "Geode Team",
1313
"description": "Developer tools for Geode",
14+
"api": { "include": ["include/*.hpp"] },
1415
"links": {
1516
"source": "https://github.com/geode-sdk/DevTools"
1617
},

0 commit comments

Comments
 (0)