// // #include "DebugWindow.h" #include "globalincs/alphacolors.h" #include "osapi/osapi.h" namespace { SDL_DisplayID get_debug_display() { // If we have multiple displays then try to put the debug window on the one // that isn't used by the main game window auto primaryDisplay = SDL_GetPrimaryDisplay(); auto gameDisplay = SDL_GetDisplayForWindow(os::getSDLMainWindow()); if (primaryDisplay != gameDisplay) { return primaryDisplay; } auto debugDisplay = primaryDisplay; auto displays = SDL_GetDisplays(nullptr); if (displays) { for (int i = 0; displays[i]; ++i) { if (displays[i] != gameDisplay) { debugDisplay = displays[i]; break; } } SDL_free(displays); } return debugDisplay; } } namespace osapi { DebugWindow::DebugWindow() { os::ViewPortProperties attrs = {}; uint32_t display = get_debug_display(); attrs.display = display; SDL_Rect size; SDL_GetDisplayBounds(display, &size); // Make the window a bit smaller so that it doesn't take up the whole screen attrs.width = (uint32_t) (size.w / 1.3f); attrs.height = (uint32_t) (size.h / 1.3f); attrs.title = "FreeSpace Open - Debug Window"; attrs.flags.set(os::ViewPortFlags::Resizeable); // Make this window resizeable auto debugView = gr_create_viewport(attrs); if (debugView) { debug_view = os::addViewport(std::move(debugView)); debug_sdl_window = debug_view->toSDLWindow(); if (debug_sdl_window != nullptr) { os::events::addEventListener(SDL_EVENT_KEY_UP, os::events::DEFAULT_LISTENER_WEIGHT - 5, [this](const SDL_Event& e) { return this->debug_key_handler(e); }); } } // try and return focus to the game window (this may be ignored by the DE) if (debug_sdl_window != nullptr && os::getSDLMainWindow() != nullptr) { SDL_RaiseWindow(os::getSDLMainWindow()); } } DebugWindow::~DebugWindow() { } void DebugWindow::doFrame(float) { if (!debug_view) { // Failed to create debug window, nothing to do here return; } gr_use_viewport(debug_view); gr_clear(); font::set_font(font::FONT1); if (max_category_width != 0) { // Draw a line to separate the category from the text auto x_pos = max_category_width + 14; gr_set_color_fast(&Color_grey); gr_line(x_pos, 0, x_pos, gr_screen.max_h, GR_RESIZE_NONE); } // Print the saved debug log lines from the bottom up float current_y = i2fl(gr_screen.max_h - 10); // If the current view offset is at the end of the list then we also need to display the line that is currently being constructed if (log_view_offset == lines.size() - 1) { if (!current_line.empty()) { LineInfo info; info.text = current_line; info.category = current_category; current_y = print_line(current_y, info); } } // Now print all the stored lines // Backwards iteration of the vector comes from here: http://stackoverflow.com/a/4206815 for (auto i = log_view_offset + 1; i-- > 0;) { auto& line = lines[i]; current_y = print_line(current_y, line); if (current_y < 0.f) { // End to iteration if the rendered string would be invisible break; } } // Don't run scripting here gr_flip(false); gr_use_viewport(os::getMainViewport()); } float DebugWindow::print_line(float bottom_y, const LineInfo& line) { int category_width; gr_get_string_size(&category_width, nullptr, line.category.c_str()); // Wrap the string to make sure everything can be read SCP_vector split_lines; SCP_vector line_lengths; // Substract 40 so that we can have margins on both sides // Make sure that the width doesn't go too low or else split_str will not be able to fit enough characters in one line auto max_w = std::max(40, gr_screen.max_w - max_category_width - 40); split_str(line.text.c_str(), max_w, line_lengths, split_lines); auto text_height = split_lines.size() * font::get_current_font()->getHeight(); float y_pos = bottom_y - text_height; float cat_x_pos = max_category_width - category_width + 10.f; // Give each category a unique color. We do this by hashing the string and using that to construct the RGB values auto hash = std::hash()(line.category); gr_set_color((int) (hash & 0xFF), (int) ((hash & 0xFF00) >> 8), (int) ((hash & 0xFF0000) >> 16)); gr_string(cat_x_pos, y_pos, line.category.c_str(), GR_RESIZE_NONE); gr_set_color_fast(&Color_white); for (size_t i = 0; i < split_lines.size(); ++i) { gr_string(max_category_width + 18.f, y_pos, split_lines[i], GR_RESIZE_NONE, 1.0f, static_cast(line_lengths[i])); y_pos += font::get_current_font()->getHeight(); } return bottom_y - text_height; } bool DebugWindow::debug_key_handler(const SDL_Event& evt) { if (!os::events::isWindowEvent(evt, debug_sdl_window)) { // Event belongs to another window return false; } ptrdiff_t diff = 0; switch(evt.key.key) { case SDLK_DOWN: diff = 1; break; case SDLK_UP: diff = -1; break; case SDLK_PAGEDOWN: diff = 60; // It's not actually a page but this should still work break; case SDLK_PAGEUP: diff = -60; break; case SDLK_END: log_view_offset = lines.size() - 1; return true; case SDLK_HOME: log_view_offset = 0; return true; default: return true; } auto prev = log_view_offset; log_view_offset += diff; if (diff < 0 && log_view_offset > prev) { // We had an overflow of the counter log_view_offset = 0; } log_view_offset = std::min(log_view_offset, lines.size() - 1); return true; } void DebugWindow::addDebugMessage(const char* category, const char* text) { if (!debug_view) { // Failed to create debug window, nothing to do here return; } if (category != current_category) { // There is a new category so we need to write the old string to our log // Find finished lines and add them to our vector split_current_and_add_to_log(category); if (!current_line.empty()) { // Write the last line to our log LineInfo info; info.text = current_line; info.category = category; addToLog(std::move(info)); current_line.clear(); } } current_category = category; current_line += text; split_current_and_add_to_log(category); } void DebugWindow::split_current_and_add_to_log(const SCP_string& category) { size_t pos; while ((pos = current_line.find('\n')) != SCP_string::npos) { LineInfo info; info.text = current_line.substr(0, pos); info.category = category; addToLog(std::move(info)); // Since pos refers to a valid position in the string, the expression pos + 1 is always valid since substr // allows values up to str.size() current_line = current_line.substr(pos + 1); } } void DebugWindow::addToLog(LineInfo&& line) { int cat_width; gr_get_string_size(&cat_width, nullptr, line.category.c_str()); max_category_width = std::max(cat_width, max_category_width); if (log_view_offset == lines.size() - 1) { // We are currently showing the last line so we need to follow new entries ++log_view_offset; } lines.push_back(std::move(line)); } }