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+ }
0 commit comments