#include "tools/jotpluggler/internal.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" #include "imgui_impl_opengl3_loader.h" #include namespace fs = std::filesystem; void draw_fps_overlay(const UiState &state, float top_offset) { if (!state.show_fps_overlay) { return; } ImGuiViewport *viewport = ImGui::GetMainViewport(); const ImGuiIO &io = ImGui::GetIO(); const float fps = io.Framerate; const std::string label = util::string_format("%.1f fps", fps); const ImVec2 padding(10.0f, 8.0f); const ImVec2 margin(12.0f, 10.0f); app_push_mono_font(); ImFont *font = ImGui::GetFont(); const float font_size = ImGui::GetFontSize(); const ImVec2 text_size = ImGui::CalcTextSize(label.c_str()); app_pop_mono_font(); const ImVec2 size(text_size.x + padding.x * 2.0f, text_size.y + padding.y * 2.0f); const ImVec2 pos(viewport->Pos.x + viewport->Size.x - size.x - margin.x, viewport->Pos.y + top_offset + margin.y); ImDrawList *draw_list = ImGui::GetForegroundDrawList(viewport); const ImVec2 max(pos.x + size.x, pos.y + size.y); draw_list->AddRectFilled(pos, max, ImGui::GetColorU32(color_rgb(248, 249, 251, 0.92f)), 4.0f); draw_list->AddRect(pos, max, ImGui::GetColorU32(color_rgb(182, 188, 196, 0.95f)), 4.0f); draw_list->AddText(font, font_size, ImVec2(pos.x + padding.x, pos.y + padding.y), ImGui::GetColorU32(color_rgb(57, 62, 69)), label.c_str(), nullptr); } void render_layout(AppSession *session, UiState *state, bool show_camera_feed) { if (!state->fps_overlay_initialized) { state->show_fps_overlay = false; state->fps_overlay_initialized = true; } ensure_shared_range(state, *session); if (state->follow_latest) { update_follow_range(state, *session); state->suppress_range_side_effects = true; } else { clamp_shared_range(state, *session); } const bool ctrl = ImGui::GetIO().KeyCtrl || ImGui::GetIO().KeySuper; const bool shift = ImGui::GetIO().KeyShift; if (!ImGui::GetIO().WantTextInput && ctrl && ImGui::IsKeyPressed(ImGuiKey_Z, false)) { if (shift) { apply_redo(session, state); } else { apply_undo(session, state); } } if (!ImGui::GetIO().WantTextInput && ctrl && ImGui::IsKeyPressed(ImGuiKey_F, false)) { state->open_find_signal = true; } if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false)) { step_tracker(state, -1.0); } if (ImGui::IsKeyPressed(ImGuiKey_RightArrow, false)) { step_tracker(state, 1.0); } if (!ImGui::GetIO().WantTextInput && ImGui::IsKeyPressed(ImGuiKey_Space, false)) { state->playback_playing = !state->playback_playing; } advance_playback(state, *session); CameraFeedView *sidebar_camera = session->pane_camera_feeds[static_cast(sidebar_preview_camera_view(*session))].get(); if (show_camera_feed && sidebar_camera != nullptr && state->has_tracker_time) { sidebar_camera->update(state->tracker_time); } const float menu_height = draw_main_menu_bar(session, state); UiMetrics ui = compute_ui_metrics(ImGui::GetMainViewport()->Size, menu_height, state->sidebar_width); if (state->browser_nodes_dirty) { rebuild_browser_nodes(session, state); state->browser_nodes_dirty = false; } state->sidebar_width = ui.sidebar_width; draw_sidebar(session, ui, state, show_camera_feed); draw_workspace(session, ui, state); draw_sidebar_resizer(ui, state); if (!state->custom_series.selected && !state->logs.selected) { draw_pane_windows(session, state); } draw_status_bar(*session, ui, state); draw_popups(session, state); draw_fps_overlay(*state, menu_height); } void save_framebuffer_png(const fs::path &output_path, int width, int height) { ensure_parent_dir(output_path); if (width <= 0 || height <= 0) throw std::runtime_error("Invalid framebuffer size"); std::vector pixels(static_cast(width) * static_cast(height) * 4U, 0); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); const fs::path ppm_path = output_path.parent_path() / (output_path.stem().string() + ".ppm"); std::string ppm = util::string_format("P6\n%d %d\n255\n", width, height); ppm.reserve(ppm.size() + static_cast(width) * static_cast(height) * 3U); for (int y = height - 1; y >= 0; --y) { for (int x = 0; x < width; ++x) { const size_t index = static_cast((y * width + x) * 4); ppm.append(reinterpret_cast(&pixels[index]), 3); } } write_file_or_throw(ppm_path, ppm.data(), ppm.size()); const std::string command = "convert " + shell_quote(ppm_path.string()) + " " + shell_quote(output_path.string()); run_system_or_throw(command, "image conversion"); fs::remove(ppm_path); } void render_frame(GLFWwindow *window, AppSession *session, UiState *state, const fs::path *capture_path) { glfwPollEvents(); int framebuffer_width = 0; int framebuffer_height = 0; glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); if (state->request_save_layout) { if (session->layout_path.empty()) { state->open_save_layout = true; } else { save_layout(session, state, session->layout_path.string()); } state->request_save_layout = false; } if (state->request_reset_layout) { reset_layout(session, state); state->request_reset_layout = false; } poll_async_route_load(session, state); if (session->data_mode == SessionDataMode::Stream && session->stream_poller) { StreamExtractBatch batch; std::string error_text; if (session->stream_poller->consume(&batch, &error_text)) { if (!error_text.empty()) { state->error_text = error_text; state->open_error_popup = true; state->status_text = "Stream disconnected"; } else { apply_stream_batch(session, state, std::move(batch)); } } } const bool show_camera = capture_path == nullptr && session->data_mode != SessionDataMode::Stream; render_layout(session, state, show_camera); ImGui::Render(); if (state->request_close) { glfwSetWindowShouldClose(window, GLFW_TRUE); state->request_close = false; } glViewport(0, 0, framebuffer_width, framebuffer_height); glClearColor(227.0f / 255.0f, 229.0f / 255.0f, 233.0f / 255.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); if (capture_path != nullptr) { save_framebuffer_png(*capture_path, framebuffer_width, framebuffer_height); } glfwSwapBuffers(window); state->suppress_range_side_effects = false; }