3
0

DebugConsole.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <DebugConsole.h>
  9. #if defined(IMGUI_ENABLED)
  10. #include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
  11. #include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
  12. #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
  13. #include <AzFramework/Input/Devices/Touch/InputDeviceTouch.h>
  14. #include <AzFramework/Input/Mappings/InputMappingAnd.h>
  15. #include <AzFramework/Input/Mappings/InputMappingOr.h>
  16. #include <AzCore/Console/IConsole.h>
  17. #include <AzCore/Interface/Interface.h>
  18. #include <Atom/Feature/ImGui/SystemBus.h>
  19. #include <ImGuiContextScope.h>
  20. #include <ImGui/ImGuiPass.h>
  21. #include <imgui/imgui.h>
  22. using namespace AzFramework;
  23. namespace AZ
  24. {
  25. AZ_CVAR(bool, bg_showDebugConsole, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Enables or disables the debug console within imGui");
  26. AZ_CVAR(float, bg_defaultDebugConsoleWidth, 960.f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The default width for the imGui debug console");
  27. AZ_CVAR(float, bg_defaultDebugConsoleHeight, 480.f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The default height for the imGui debug console");
  28. AZ::Color GetColorForLogLevel(const AZ::LogLevel& logLevel)
  29. {
  30. switch (logLevel)
  31. {
  32. case AZ::LogLevel::Fatal:
  33. case AZ::LogLevel::Error:
  34. {
  35. return AZ::Colors::Red;
  36. }
  37. break;
  38. case AZ::LogLevel::Warn:
  39. {
  40. return AZ::Colors::Yellow;
  41. }
  42. break;
  43. case AZ::LogLevel::Notice:
  44. case AZ::LogLevel::Info:
  45. case AZ::LogLevel::Debug:
  46. case AZ::LogLevel::Trace:
  47. default:
  48. {
  49. return AZ::Colors::White;
  50. }
  51. break;
  52. }
  53. }
  54. void ResetTextInputField(ImGuiInputTextCallbackData* data, const AZStd::string& newText)
  55. {
  56. data->DeleteChars(0, data->BufTextLen);
  57. data->InsertChars(0, newText.c_str());
  58. }
  59. int InputTextCallback(ImGuiInputTextCallbackData* data)
  60. {
  61. DebugConsole* debugConsole = static_cast<DebugConsole*>(data->UserData);
  62. if (!debugConsole)
  63. {
  64. return 0;
  65. }
  66. switch (data->EventFlag)
  67. {
  68. case ImGuiInputTextFlags_CallbackCompletion:
  69. {
  70. debugConsole->AutoCompleteCommand(data);
  71. }
  72. break;
  73. case ImGuiInputTextFlags_CallbackHistory:
  74. {
  75. debugConsole->BrowseInputHistory(data);
  76. }
  77. break;
  78. }
  79. return 0;
  80. }
  81. DebugConsole::DebugConsole(int maxEntriesToDisplay, int maxInputHistorySize)
  82. : m_maxEntriesToDisplay(maxEntriesToDisplay)
  83. , m_maxInputHistorySize(maxInputHistorySize)
  84. {
  85. // The debug console is currently only supported when running the standalone launcher.
  86. // It does function correctly when running the editor if you remove this check, but it
  87. // conflicts with the legacy debug console that also shows at the bottom of the editor.
  88. AZ::ApplicationTypeQuery applicationType;
  89. AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::QueryApplicationType, applicationType);
  90. if (!applicationType.IsGame())
  91. {
  92. return;
  93. }
  94. ImGui::ImGuiUpdateListenerBus::Handler::BusConnect();
  95. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  96. }
  97. DebugConsole::~DebugConsole()
  98. {
  99. AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
  100. ImGui::ImGuiUpdateListenerBus::Handler::BusDisconnect();
  101. }
  102. void DebugConsole::OnImGuiMainMenuUpdate()
  103. {
  104. }
  105. void DebugConsole::OnImGuiUpdate()
  106. {
  107. if (!bg_showDebugConsole)
  108. {
  109. return;
  110. }
  111. // Draw the debug console in a closeable, moveable, and resizeable IMGUI window.
  112. bool continueShowing = bg_showDebugConsole;
  113. ImGui::SetNextWindowSize(ImVec2(bg_defaultDebugConsoleWidth, bg_defaultDebugConsoleHeight), ImGuiCond_Once);
  114. if (!ImGui::Begin("Debug Console", &continueShowing))
  115. {
  116. ImGui::End();
  117. return;
  118. }
  119. bg_showDebugConsole = continueShowing;
  120. // Show a scrolling child region in which to display all debug log entires.
  121. const float footerHeightToReserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetStyle().FramePadding.y + ImGui::GetFrameHeightWithSpacing();
  122. ImGui::BeginChild("DebugLogEntriesScrollBox", ImVec2(0, -footerHeightToReserve), false, ImGuiWindowFlags_HorizontalScrollbar);
  123. {
  124. // Display each debug log entry individually so they can be colored.
  125. for (const auto& debugLogEntry : m_debugLogEntires)
  126. {
  127. const ImVec4 color(debugLogEntry.second.GetR(),
  128. debugLogEntry.second.GetG(),
  129. debugLogEntry.second.GetB(),
  130. debugLogEntry.second.GetA());
  131. ImGui::PushStyleColor(ImGuiCol_Text, color);
  132. ImGui::TextUnformatted(debugLogEntry.first.c_str());
  133. ImGui::PopStyleColor();
  134. }
  135. // Scroll to the last debug log entry if needed.
  136. if (m_forceScroll || (m_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()))
  137. {
  138. ImGui::SetScrollHereY(1.0f);
  139. m_forceScroll = false;
  140. }
  141. }
  142. ImGui::EndChild();
  143. // Show a text input field.
  144. ImGui::Separator();
  145. const ImGuiInputTextFlags inputTextFlags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory;
  146. const bool textWasInput = ImGui::InputText("", m_inputBuffer, IM_ARRAYSIZE(m_inputBuffer), inputTextFlags, &InputTextCallback, (void*)this);
  147. if (textWasInput)
  148. {
  149. OnTextInputEntered(m_inputBuffer);
  150. azstrncpy(m_inputBuffer, IM_ARRAYSIZE(m_inputBuffer), "", 1);
  151. ImGui::SetKeyboardFocusHere(-1);
  152. m_forceScroll = true;
  153. }
  154. // Focus on the text input field.
  155. if (ImGui::IsWindowAppearing())
  156. {
  157. ImGui::SetKeyboardFocusHere(-1);
  158. }
  159. ImGui::SetItemDefaultFocus();
  160. // Show a button to clear the debug log.
  161. ImGui::SameLine();
  162. if (ImGui::Button("Clear"))
  163. {
  164. ClearDebugLog();
  165. }
  166. // Show an options menu.
  167. if (ImGui::BeginPopup("Options"))
  168. {
  169. // Show a combo box that controls the minimum log level (options correspond to AZ::LogLevel).
  170. ImGui::SetNextItemWidth((ImGui::CalcTextSize("WWWWWW").x + ImGui::GetStyle().FramePadding.x) * 2.0f);
  171. int logLevel = static_cast<int>(AZ::Interface<AZ::ILogger>::Get()->GetLogLevel());
  172. if (ImGui::Combo("Minimum Log Level", &logLevel, "All\0Trace\0Debug\0Info\0Notice\0Warn\0Error\0Fatal\0\0"))
  173. {
  174. logLevel = AZStd::clamp(logLevel, static_cast<int>(AZ::LogLevel::Trace), static_cast<int>(AZ::LogLevel::Fatal));
  175. AZ::Interface<AZ::ILogger>::Get()->SetLogLevel(static_cast<AZ::LogLevel>(logLevel));
  176. }
  177. // Show a checkbox that controls whether to auto scroll when new debug log entires are added.
  178. ImGui::Checkbox("Auto Scroll New Log Entries", &m_autoScroll);
  179. ImGui::EndPopup();
  180. }
  181. // Show a button to open the options menu.
  182. ImGui::SameLine();
  183. if (ImGui::Button("Options"))
  184. {
  185. ImGui::OpenPopup("Options");
  186. }
  187. ImGui::End();
  188. }
  189. void DebugConsole::AddDebugLog(const AZStd::string& debugLogString, const AZ::Color& color)
  190. {
  191. // Add the debug to our display, removing the oldest entry if we exceed the maximum.
  192. m_debugLogEntires.push_back(AZStd::make_pair(debugLogString, color));
  193. if (m_debugLogEntires.size() > m_maxEntriesToDisplay)
  194. {
  195. m_debugLogEntires.pop_front();
  196. }
  197. }
  198. void DebugConsole::AddDebugLog(const char* window, const char* debugLogString, AZ::LogLevel logLevel)
  199. {
  200. AZ::ILogger* logger = AZ::Interface<AZ::ILogger>::Get();
  201. if (logger == nullptr || logLevel < logger->GetLogLevel())
  202. {
  203. return;
  204. }
  205. AZ::Color color = GetColorForLogLevel(logLevel);
  206. if (strcmp(window, AZ::Debug::Trace::GetDefaultSystemWindow()) == 0)
  207. {
  208. AddDebugLog(debugLogString, color);
  209. }
  210. else
  211. {
  212. AddDebugLog(AZStd::string::format("(%s) - %s", window, debugLogString), color);
  213. }
  214. }
  215. void DebugConsole::ClearDebugLog()
  216. {
  217. m_debugLogEntires.clear();
  218. }
  219. bool DebugConsole::OnPreError(const char* window, [[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, const char* message)
  220. {
  221. AddDebugLog(window, message, AZ::LogLevel::Error);
  222. return false;
  223. }
  224. bool DebugConsole::OnPreWarning(const char* window, [[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, const char* message)
  225. {
  226. AddDebugLog(window, message, AZ::LogLevel::Warn);
  227. return false;
  228. }
  229. bool DebugConsole::OnPrintf(const char* window, const char* message)
  230. {
  231. AddDebugLog(window, message, AZ::LogLevel::Notice); // Notice is one level below warning
  232. return false;
  233. }
  234. void DebugConsole::AutoCompleteCommand(ImGuiInputTextCallbackData* data)
  235. {
  236. AZStd::vector<AZStd::string> matchingCommands;
  237. const AZStd::string longestMatchingSubstring = AZ::Interface<AZ::IConsole>::Get()->AutoCompleteCommand(data->Buf, &matchingCommands);
  238. ResetTextInputField(data, longestMatchingSubstring);
  239. // Auto complete options are logged in AutoCompleteCommand using AZLOG_INFO,
  240. // so if the log level is set higher we display auto complete options here.
  241. if (AZ::Interface<AZ::ILogger>::Get()->GetLogLevel() > AZ::LogLevel::Info)
  242. {
  243. if (matchingCommands.empty())
  244. {
  245. AZStd::string noAutoCompletionResults("No auto completion options: ");
  246. noAutoCompletionResults += data->Buf;
  247. AddDebugLog(noAutoCompletionResults, AZ::Colors::Gray);
  248. }
  249. else if (matchingCommands.size() > 1)
  250. {
  251. AZStd::string autoCompletionResults("Auto completion options: ");
  252. autoCompletionResults += data->Buf;
  253. AddDebugLog(autoCompletionResults, AZ::Colors::Green);
  254. for (const AZStd::string& matchingCommand : matchingCommands)
  255. {
  256. AddDebugLog(matchingCommand, AZ::Colors::Green);
  257. }
  258. }
  259. }
  260. }
  261. void DebugConsole::BrowseInputHistory(ImGuiInputTextCallbackData* data)
  262. {
  263. const int previousHistoryIndex = m_currentHistoryIndex;
  264. const int maxHistoryIndex = static_cast<int>(m_textInputHistory.size() - 1);
  265. switch (data->EventKey)
  266. {
  267. // Browse backwards through the history.
  268. case ImGuiKey_UpArrow:
  269. {
  270. if (m_currentHistoryIndex < 0)
  271. {
  272. // Go to the last history entry.
  273. m_currentHistoryIndex = maxHistoryIndex;
  274. }
  275. else if (m_currentHistoryIndex > 0)
  276. {
  277. // Go to the previous history entry.
  278. --m_currentHistoryIndex;
  279. }
  280. }
  281. break;
  282. // Browse forwards through the history.
  283. case ImGuiKey_DownArrow:
  284. {
  285. if (m_currentHistoryIndex >= 0 && m_currentHistoryIndex < maxHistoryIndex)
  286. {
  287. ++m_currentHistoryIndex;
  288. }
  289. }
  290. break;
  291. }
  292. if (previousHistoryIndex != m_currentHistoryIndex)
  293. {
  294. ResetTextInputField(data, m_textInputHistory[m_currentHistoryIndex]);
  295. }
  296. }
  297. void DebugConsole::OnTextInputEntered(const char* inputText)
  298. {
  299. // Add the input text to our history, removing the oldest entry if we exceed the maximum.
  300. const AZStd::string inputTextString(inputText);
  301. m_textInputHistory.push_back(inputTextString);
  302. if (m_textInputHistory.size() > m_maxInputHistorySize)
  303. {
  304. m_textInputHistory.pop_front();
  305. }
  306. // Clear the current history index;
  307. m_currentHistoryIndex = -1;
  308. // Attempt to perform a console command.
  309. AZ::Interface<AZ::IConsole>::Get()->PerformCommand(inputTextString.c_str());
  310. }
  311. }
  312. #endif // defined(IMGUI_ENABLED)