2
0

DebuggerPlugin.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. #include "DebuggerPlugin.h"
  2. #include "../../Include/RmlUi/Core/Context.h"
  3. #include "../../Include/RmlUi/Core/Core.h"
  4. #include "../../Include/RmlUi/Core/ElementInstancer.h"
  5. #include "../../Include/RmlUi/Core/ElementUtilities.h"
  6. #include "../../Include/RmlUi/Core/Factory.h"
  7. #include "../../Include/RmlUi/Core/Types.h"
  8. #include "DebuggerSystemInterface.h"
  9. #include "ElementContextHook.h"
  10. #include "ElementDataModels.h"
  11. #include "ElementDebugDocument.h"
  12. #include "ElementInfo.h"
  13. #include "ElementLog.h"
  14. #include "FontSource.h"
  15. #include "Geometry.h"
  16. #include "MenuSource.h"
  17. #include <stack>
  18. namespace Rml {
  19. namespace Debugger {
  20. DebuggerPlugin* DebuggerPlugin::instance = nullptr;
  21. DebuggerPlugin::DebuggerPlugin()
  22. {
  23. RMLUI_ASSERT(instance == nullptr);
  24. instance = this;
  25. host_context = nullptr;
  26. debug_context = nullptr;
  27. log_interface = nullptr;
  28. menu_element = nullptr;
  29. info_element = nullptr;
  30. log_element = nullptr;
  31. data_explorer_element = nullptr;
  32. hook_element = nullptr;
  33. render_outlines = false;
  34. application_interface = nullptr;
  35. }
  36. DebuggerPlugin::~DebuggerPlugin()
  37. {
  38. instance = nullptr;
  39. }
  40. bool DebuggerPlugin::Initialise(Context* context)
  41. {
  42. host_context = context;
  43. Geometry::SetContext(context);
  44. if (!LoadFont())
  45. {
  46. Log::Message(Log::LT_ERROR, "Failed to initialise debugger, unable to load font.");
  47. return false;
  48. }
  49. if (!LoadMenuElement() || !LoadInfoElement() || !LoadLogElement() || !LoadDataExplorerElement())
  50. {
  51. Log::Message(Log::LT_ERROR, "Failed to initialise debugger, error while load debugger elements.");
  52. return false;
  53. }
  54. hook_element_instancer = MakeUnique<ElementInstancerGeneric<ElementContextHook>>();
  55. Factory::RegisterElementInstancer("debug-hook", hook_element_instancer.get());
  56. return true;
  57. }
  58. bool DebuggerPlugin::SetContext(Context* context)
  59. {
  60. if (debug_context && hook_element)
  61. {
  62. debug_context->UnloadDocument(hook_element);
  63. hook_element = nullptr;
  64. }
  65. if (context)
  66. {
  67. ElementDocument* element = context->CreateDocument("debug-hook");
  68. if (!element)
  69. return false;
  70. RMLUI_ASSERT(!hook_element);
  71. hook_element = rmlui_dynamic_cast<ElementContextHook*>(element);
  72. if (!hook_element)
  73. {
  74. context->UnloadDocument(element);
  75. return false;
  76. }
  77. hook_element->Initialise(this);
  78. }
  79. if (info_element)
  80. {
  81. SetupInfoListeners(context);
  82. info_element->Reset();
  83. }
  84. if (data_explorer_element)
  85. {
  86. data_explorer_element->SetDebugContext(context);
  87. }
  88. debug_context = context;
  89. return true;
  90. }
  91. void DebuggerPlugin::SetVisible(bool visibility)
  92. {
  93. if (visibility)
  94. menu_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible));
  95. else
  96. menu_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
  97. }
  98. bool DebuggerPlugin::IsVisible()
  99. {
  100. return menu_element->IsVisible();
  101. }
  102. void DebuggerPlugin::Render()
  103. {
  104. // Render the outlines of the debug context's elements.
  105. if (render_outlines && debug_context)
  106. {
  107. for (int i = 0; i < debug_context->GetNumDocuments(); ++i)
  108. {
  109. ElementDocument* document = debug_context->GetDocument(i);
  110. if (document->GetId().find("rmlui-debug-") == 0)
  111. continue;
  112. Stack<Element*> element_stack;
  113. element_stack.push(document);
  114. while (!element_stack.empty())
  115. {
  116. Element* element = element_stack.top();
  117. element_stack.pop();
  118. if (element->IsVisible())
  119. {
  120. ElementUtilities::ApplyTransform(*element);
  121. for (int j = 0; j < element->GetNumBoxes(); ++j)
  122. {
  123. const RenderBox box = element->GetRenderBox(BoxArea::Border, j);
  124. Geometry::RenderOutline(element->GetAbsoluteOffset(BoxArea::Border) + box.GetBorderOffset(), box.GetFillSize(),
  125. Colourb(255, 0, 0, 128), 1);
  126. }
  127. for (int j = 0; j < element->GetNumChildren(); ++j)
  128. element_stack.push(element->GetChild(j));
  129. }
  130. }
  131. }
  132. }
  133. // Render the info element's boxes.
  134. if (info_element && info_element->IsVisible())
  135. {
  136. info_element->RenderHoverElement();
  137. info_element->RenderSourceElement();
  138. }
  139. }
  140. void DebuggerPlugin::OnShutdown()
  141. {
  142. // Release the elements before we leak track, this ensures the debugger hook has been cleared
  143. // and that we don't try send the messages to the debug log window
  144. ReleaseElements();
  145. hook_element_instancer.reset();
  146. delete this;
  147. }
  148. void DebuggerPlugin::OnContextDestroy(Context* context)
  149. {
  150. if (context == debug_context)
  151. {
  152. // The context we're debugging is being destroyed, so we need to remove our debug hook elements.
  153. SetContext(nullptr);
  154. }
  155. if (context == host_context)
  156. {
  157. // Our host is being destroyed, so we need to shut down the debugger.
  158. ReleaseElements();
  159. Geometry::SetContext(nullptr);
  160. host_context = nullptr;
  161. }
  162. }
  163. void DebuggerPlugin::OnElementDestroy(Element* element)
  164. {
  165. // Detect external destruction of the debugger documents. This can happen for example if the user calls
  166. // `Context::UnloadAllDocuments()` on the host context.
  167. if (element == menu_element || element == info_element || element == log_element || element == data_explorer_element)
  168. {
  169. ReleaseElements();
  170. Log::Message(Log::LT_ERROR,
  171. "A document owned by the Debugger plugin was destroyed externally. This is not allowed. Consider shutting down the debugger instead.");
  172. }
  173. if (info_element)
  174. info_element->OnElementDestroy(element);
  175. }
  176. void DebuggerPlugin::ProcessEvent(Event& event)
  177. {
  178. struct ButtonIdToDocumentMapping {
  179. String id;
  180. ElementDocument* document;
  181. };
  182. const ButtonIdToDocumentMapping button_mappings[] = {
  183. {"event-log-button", log_element},
  184. {"debug-info-button", info_element},
  185. {"data-models-button", data_explorer_element},
  186. };
  187. if (event == EventId::Click)
  188. {
  189. for (const ButtonIdToDocumentMapping& button_mapping : button_mappings)
  190. {
  191. if (event.GetTargetElement()->GetId() == button_mapping.id)
  192. {
  193. if (button_mapping.document->IsVisible())
  194. button_mapping.document->Hide();
  195. else
  196. button_mapping.document->Show();
  197. }
  198. }
  199. if (event.GetTargetElement()->GetId() == "outlines-button")
  200. {
  201. render_outlines = !render_outlines;
  202. event.GetTargetElement()->SetClass("open", render_outlines);
  203. }
  204. }
  205. else if (event == EventId::Hide || event == EventId::Show)
  206. {
  207. for (const ButtonIdToDocumentMapping& button_mapping : button_mappings)
  208. {
  209. if (event.GetTargetElement() == button_mapping.document)
  210. {
  211. Element* button = menu_element->GetElementById(button_mapping.id);
  212. const bool set_open = (event == EventId::Show);
  213. button->SetClass("open", set_open);
  214. }
  215. }
  216. }
  217. }
  218. DebuggerPlugin* DebuggerPlugin::GetInstance()
  219. {
  220. return instance;
  221. }
  222. bool DebuggerPlugin::LoadFont()
  223. {
  224. const String font_family_name = "rmlui-debugger-font";
  225. return (LoadFontFace({courier_prime_code, sizeof(courier_prime_code)}, font_family_name, Style::FontStyle::Normal, Style::FontWeight::Normal) &&
  226. LoadFontFace({courier_prime_code_italic, sizeof(courier_prime_code_italic)}, font_family_name, Style::FontStyle::Italic,
  227. Style::FontWeight::Normal));
  228. }
  229. bool DebuggerPlugin::LoadMenuElement()
  230. {
  231. debug_document_instancer = MakeUnique<ElementInstancerGeneric<ElementDebugDocument>>();
  232. Factory::RegisterElementInstancer("debug-document", debug_document_instancer.get());
  233. menu_element = host_context->CreateDocument("debug-document");
  234. if (!menu_element)
  235. return false;
  236. menu_element->SetId("rmlui-debug-menu");
  237. menu_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
  238. menu_element->SetInnerRML(menu_rml);
  239. SharedPtr<StyleSheetContainer> style_sheet = Factory::InstanceStyleSheetString(menu_rcss);
  240. if (!style_sheet)
  241. {
  242. host_context->UnloadDocument(menu_element);
  243. menu_element = nullptr;
  244. return false;
  245. }
  246. menu_element->SetStyleSheetContainer(std::move(style_sheet));
  247. menu_element->GetElementById("version-number")->SetInnerRML(Rml::GetVersion());
  248. for (auto* id : {"event-log-button", "debug-info-button", "outlines-button", "data-models-button"})
  249. {
  250. Element* button = menu_element->GetElementById(id);
  251. button->AddEventListener(EventId::Click, this);
  252. }
  253. return true;
  254. }
  255. bool DebuggerPlugin::LoadInfoElement()
  256. {
  257. info_element_instancer = MakeUnique<ElementInstancerGeneric<ElementInfo>>();
  258. Factory::RegisterElementInstancer("debug-info", info_element_instancer.get());
  259. info_element = rmlui_dynamic_cast<ElementInfo*>(host_context->CreateDocument("debug-info"));
  260. if (!info_element)
  261. return false;
  262. info_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
  263. if (!info_element->Initialise())
  264. {
  265. host_context->UnloadDocument(info_element);
  266. info_element = nullptr;
  267. return false;
  268. }
  269. info_element->AddEventListener(EventId::Hide, this);
  270. info_element->AddEventListener(EventId::Show, this);
  271. return true;
  272. }
  273. bool DebuggerPlugin::LoadLogElement()
  274. {
  275. log_element_instancer = MakeUnique<ElementInstancerGeneric<ElementLog>>();
  276. Factory::RegisterElementInstancer("debug-log", log_element_instancer.get());
  277. log_element = rmlui_dynamic_cast<ElementLog*>(host_context->CreateDocument("debug-log"));
  278. if (!log_element)
  279. return false;
  280. log_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
  281. if (!log_element->Initialise())
  282. {
  283. host_context->UnloadDocument(log_element);
  284. log_element = nullptr;
  285. return false;
  286. }
  287. log_element->AddEventListener(EventId::Hide, this);
  288. log_element->AddEventListener(EventId::Show, this);
  289. // Make the system interface; this will trap the log messages for us.
  290. application_interface = Rml::GetSystemInterface();
  291. log_interface = MakeUnique<DebuggerSystemInterface>(application_interface, log_element);
  292. Rml::SetSystemInterface(log_interface.get());
  293. return true;
  294. }
  295. bool DebuggerPlugin::LoadDataExplorerElement()
  296. {
  297. data_explorer_element_instancer = MakeUnique<ElementInstancerGeneric<ElementDataModels>>();
  298. Factory::RegisterElementInstancer("debug-data-models", data_explorer_element_instancer.get());
  299. data_explorer_element = rmlui_dynamic_cast<ElementDataModels*>(host_context->CreateDocument("debug-data-models"));
  300. if (!data_explorer_element)
  301. return false;
  302. data_explorer_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
  303. if (!data_explorer_element->Initialise(debug_context))
  304. {
  305. host_context->UnloadDocument(data_explorer_element);
  306. data_explorer_element = nullptr;
  307. return false;
  308. }
  309. data_explorer_element->AddEventListener(EventId::Hide, this);
  310. data_explorer_element->AddEventListener(EventId::Show, this);
  311. return true;
  312. }
  313. void DebuggerPlugin::SetupInfoListeners(Rml::Context* new_context)
  314. {
  315. RMLUI_ASSERT(info_element);
  316. if (debug_context)
  317. {
  318. debug_context->RemoveEventListener("click", info_element, true);
  319. debug_context->RemoveEventListener("mouseover", info_element, true);
  320. debug_context->RemoveEventListener("mouseout", info_element, true);
  321. }
  322. if (new_context)
  323. {
  324. new_context->AddEventListener("click", info_element, true);
  325. new_context->AddEventListener("mouseover", info_element, true);
  326. new_context->AddEventListener("mouseout", info_element, true);
  327. }
  328. }
  329. void DebuggerPlugin::ReleaseElements()
  330. {
  331. // Erase event listeners to prevent crashes.
  332. if (info_element)
  333. SetupInfoListeners(nullptr);
  334. if (host_context)
  335. {
  336. if (menu_element)
  337. {
  338. host_context->UnloadDocument(menu_element);
  339. menu_element = nullptr;
  340. }
  341. if (info_element)
  342. {
  343. host_context->UnloadDocument(info_element);
  344. info_element = nullptr;
  345. }
  346. if (log_element)
  347. {
  348. host_context->UnloadDocument(log_element);
  349. log_element = nullptr;
  350. Rml::SetSystemInterface(application_interface);
  351. application_interface = nullptr;
  352. log_interface.reset();
  353. }
  354. if (data_explorer_element)
  355. {
  356. host_context->UnloadDocument(data_explorer_element);
  357. data_explorer_element = nullptr;
  358. }
  359. // Update to release documents before the plugin gets deleted.
  360. // Helps avoid cleanup crashes.
  361. host_context->Update();
  362. }
  363. if (debug_context)
  364. {
  365. if (hook_element)
  366. {
  367. debug_context->UnloadDocument(hook_element);
  368. hook_element = nullptr;
  369. }
  370. // Update to release documents before the plugin gets deleted.
  371. // Helps avoid cleanup crashes.
  372. debug_context->Update();
  373. }
  374. }
  375. } // namespace Debugger
  376. } // namespace Rml