INSTINCT Code Coverage Report


Directory: src/
File: internal/gui/windows/Screenshotter.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 0 140 0.0%
Functions: 0 2 0.0%
Branches: 0 322 0.0%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 #include "Screenshotter.hpp"
10
11 #ifdef IMGUI_IMPL_OPENGL_LOADER_GL3W
12 #include <cstdlib>
13 #include <string>
14
15 #include <imgui.h>
16 #include <imgui_internal.h>
17 #include <imgui_node_editor.h>
18 #include <imgui_node_editor_internal.h>
19 #include <imgui_canvas.h>
20 namespace ed = ax::NodeEditor;
21
22 #include <fmt/core.h>
23
24 #include "internal/FlowManager.hpp"
25 #include "internal/gui/NodeEditorApplication.hpp"
26 #include "internal/gui/widgets/FileDialog.hpp"
27
28 #include "internal/gui/windows/NodeEditorStyleEditor.hpp"
29 #include "util/ImPlot.hpp"
30 #include "util/Logger.hpp"
31
32 namespace NAV::gui::windows
33 {
34
35 std::string plotScreenshotImPlotStyleFile = "implot-light.json";
36 bool copyScreenshotsToClipboard = true;
37 bool printScreenshotSaveLocation = true;
38
39 namespace
40 {
41 ImRect _screenshotNavigateRect;
42 ImRect _screenshotCaptureRect;
43 bool _showScreenshotCaptureRect = false;
44 size_t _screenshotFrameCnt = 0;
45
46 } // namespace
47 } // namespace NAV::gui::windows
48
49 ✗ void NAV::gui::windows::ShowScreenshotter(bool* show /* = nullptr*/)
50 {
51 ✗ if (_screenshotFrameCnt > 0)
52 {
53 ✗ _screenshotFrameCnt++;
54
55 ✗ if (_screenshotFrameCnt == 8)
56 {
57 ✗ ImGuiIO& io = ImGui::GetIO();
58 ✗ ImGuiScreenshotImageBuf Output(static_cast<int>(_screenshotCaptureRect.Min.x),
59 ✗ static_cast<int>(io.DisplaySize.y) - static_cast<int>(_screenshotCaptureRect.Max.y),
60 ✗ static_cast<size_t>(_screenshotCaptureRect.GetWidth()),
61 ✗ static_cast<size_t>(_screenshotCaptureRect.GetHeight()));
62
63 ✗ std::time_t t = std::time(nullptr);
64 ✗ std::tm* now = std::localtime(&t); // NOLINT(concurrency-mt-unsafe)
65
66 ✗ auto savePath = flow::GetOutputPath()
67 ✗ / fmt::format("Screenshot_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}-{:02d}.png",
68 ✗ now->tm_year + 1900, now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec);
69 ✗ Output.SaveFile(savePath.c_str());
70 ✗ if (printScreenshotSaveLocation)
71 {
72 ✗ LOG_INFO("Screenshot saved as: {}", savePath);
73 }
74
75 ✗ if (copyScreenshotsToClipboard)
76 {
77 ✗ CopyFileToClipboard(savePath.c_str());
78 }
79 ✗ }
80 ✗ else if (_screenshotFrameCnt == 10)
81 {
82 ✗ _screenshotFrameCnt = 0;
83 }
84
85 ✗ return;
86 }
87
88 ✗ if (_showScreenshotCaptureRect)
89 {
90 ✗ constexpr float thickness = 1.0F;
91 ✗ auto rect = _screenshotCaptureRect;
92 ✗ rect.Expand(thickness);
93 ✗ ImGui::GetForegroundDrawList()->AddRect(rect.Min, rect.Max, ImColor(255, 0, 0),
94 0.0F, ImDrawFlags_None, thickness);
95 }
96
97 ✗ if (!ImGui::Begin("Screenshotter", show, ImGuiWindowFlags_AlwaysAutoResize))
98 {
99 ✗ ImGui::End();
100 ✗ return;
101 }
102
103 ✗ const float ITEM_WIDTH = 200.0F * NodeEditorApplication::windowFontRatio();
104 ✗ const float ITEM_WIDTH_HALF = (ITEM_WIDTH - ImGui::GetStyle().ItemInnerSpacing.x) / 2.0F;
105
106 ✗ ImGui::TextUnformatted("Plot screenshot style: ");
107 ✗ ImGui::SameLine();
108 ✗ ImGui::SetNextItemWidth(100.0F * NodeEditorApplication::windowFontRatio());
109 ✗ if (widgets::FileDialogLoad(plotScreenshotImPlotStyleFile, "Plot screenshot config file", ".json", { ".json" },
110 ✗ flow::GetConfigPath(), 1, "ImPlotStyleEditorScreenshot"))
111 {
112 ✗ LOG_DEBUG("Plot screenshot config file changed to: {}", plotScreenshotImPlotStyleFile);
113 }
114
115 ✗ ImGui::Checkbox("Copy screenshots to clipboard", &copyScreenshotsToClipboard);
116
117 ✗ bool transparentWindows = ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w != 1.0F;
118 ✗ if (ImGui::Checkbox("Light mode", &nodeEditorLightMode))
119 {
120 ✗ ApplyDarkLightMode(NodeEditorApplication::m_colors, transparentWindows);
121 ✗ flow::ApplyChanges();
122 }
123 ✗ ImGui::SameLine();
124 ✗ bool showGridLines = ed::GetStyle().Colors[ed::StyleColor_Grid].w != 0.0F;
125 ✗ if (ImGui::Checkbox("Grid lines", &showGridLines))
126 {
127 ✗ ed::GetStyle().Colors[ed::StyleColor_Grid].w = showGridLines ? ed::Style().Colors[ed::StyleColor_Grid].w : 0.0F;
128 ✗ flow::ApplyChanges();
129 }
130 ✗ ImGui::SameLine();
131 ✗ if (ImGui::Checkbox("Transparent windows", &transparentWindows))
132 {
133 ✗ ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = transparentWindows ? ImGuiStyle().Colors[ImGuiCol_WindowBg].w : 1.0F;
134 ✗ ImGui::GetStyle().Colors[ImGuiCol_PopupBg].w = transparentWindows ? ImGuiStyle().Colors[ImGuiCol_PopupBg].w : 1.0F;
135 ✗ flow::ApplyChanges();
136 }
137
138 ✗ if (ImGui::Checkbox("Hide left pane", &NodeEditorApplication::hideLeftPane))
139 {
140 ✗ flow::ApplyChanges();
141 }
142 ✗ if (!NodeEditorApplication::hideLeftPane)
143 {
144 ✗ ImGui::SameLine();
145 ✗ ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
146 ✗ ImGui::DragFloat("Left pane width", &NodeEditorApplication::leftPaneWidth);
147 }
148 ✗ if (ImGui::Checkbox("Hide FPS count", &NodeEditorApplication::hideFPS))
149 {
150 ✗ flow::ApplyChanges();
151 }
152 ✗ ImGui::SameLine();
153 ✗ ImGui::Checkbox("Print save location", &printScreenshotSaveLocation);
154
155 ✗ ImGui::Separator();
156
157 ✗ auto* editor = reinterpret_cast<ed::Detail::EditorContext*>(ed::GetCurrentEditor());
158 ✗ if (_screenshotNavigateRect.GetArea() == 0.0F) { _screenshotNavigateRect = editor->GetContentBounds(); }
159
160 ✗ ImGui::SetNextItemOpen(true, ImGuiCond_Once);
161 ✗ if (ImGui::TreeNode("Screenshot navigate area"))
162 {
163 ✗ ImGui::TextUnformatted(fmt::format("Current Content Bounds: Min({},{}), Max({},{})",
164 ✗ editor->GetContentBounds().Min.x, editor->GetContentBounds().Min.y,
165 ✗ editor->GetContentBounds().Max.x, editor->GetContentBounds().Max.y)
166 .c_str());
167 ✗ ImGui::TextUnformatted(fmt::format("Current View: Min({},{}), Max({},{})",
168 ✗ editor->GetViewRect().Min.x, editor->GetViewRect().Min.y,
169 ✗ editor->GetViewRect().Max.x, editor->GetViewRect().Max.y)
170 .c_str());
171
172 ✗ if (ImGui::Button("Set area to content")) { _screenshotNavigateRect = editor->GetContentBounds(); }
173 ✗ ImGui::SameLine();
174 ✗ if (ImGui::Button("Set area to current view"))
175 {
176 ✗ _screenshotNavigateRect = editor->GetViewRect();
177
178 ✗ auto extend = ImMax(_screenshotNavigateRect.GetWidth(), _screenshotNavigateRect.GetHeight());
179 ✗ constexpr float c_NavigationZoomMargin = 0.1F;
180 ✗ _screenshotNavigateRect.Expand(-extend * c_NavigationZoomMargin * 0.5F);
181 }
182
183 ✗ ImGui::SetNextItemWidth(ITEM_WIDTH);
184 ✗ ImGui::DragFloat2("Min##Nav area", &_screenshotNavigateRect.Min.x);
185 ✗ ImGui::SetNextItemWidth(ITEM_WIDTH);
186 ✗ ImGui::DragFloat2("Max##Nav area", &_screenshotNavigateRect.Max.x);
187
188 ✗ if (ImGui::Button("Navigate to area"))
189 {
190 ✗ editor->NavigateTo(_screenshotNavigateRect, true);
191 }
192 ✗ ImGui::SameLine();
193
194 ✗ static float zoomFactor = editor->GetView().Scale;
195 ✗ if (ImGui::Button("Zoom"))
196 {
197 ✗ auto targetRect = editor->GetCanvas().CalcViewRect(ImGuiEx::CanvasView(editor->GetView().Origin, zoomFactor));
198 ✗ editor->NavigateTo(targetRect, true, 0.15F);
199 }
200 ✗ ImGui::SameLine();
201 ✗ ImGui::SetNextItemWidth(ITEM_WIDTH_HALF / 2.F);
202 ✗ ImGui::InputFloat("##Zoom Factor", &zoomFactor);
203 ✗ ImGui::SameLine();
204 ✗ ImGui::TextUnformatted(fmt::format("Current: {:.3f}", editor->GetView().Scale).c_str());
205
206 ✗ ImGui::TreePop();
207 }
208
209 ✗ ImGuiIO& io = ImGui::GetIO();
210 ✗ if (_screenshotCaptureRect.GetArea() == 0.0F && ImGui::GetFrameCount() > 10) { _screenshotCaptureRect.Max = io.DisplaySize; }
211
212 ✗ ImGui::SetNextItemOpen(true, ImGuiCond_Once);
213 ✗ if (ImGui::TreeNode("Screenshot capture area"))
214 {
215 ✗ if (ImGui::Button("Set to display size"))
216 {
217 ✗ _screenshotCaptureRect.Min = ImVec2();
218 ✗ _screenshotCaptureRect.Max = io.DisplaySize;
219 }
220 ✗ ImGui::SameLine();
221 ✗ ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
222 ✗ if (ImGui::BeginCombo("##Window list", "Set to window"))
223 {
224 ✗ ImVector<ImGuiWindow*>& windows = ImGui::GetCurrentContext()->Windows;
225 ✗ for (ImGuiWindow* window : windows)
226 {
227 ✗ std::string windowName = window->Name;
228 ✗ if (!window->WasActive || windowName.find('#') != std::string::npos || windowName.starts_with("Content")) { continue; }
229 ✗ const bool is_selected = window->Rect().Min == _screenshotCaptureRect.Min && window->Rect().Max == _screenshotCaptureRect.Max;
230 ✗ if (ImGui::Selectable(window->Name, is_selected))
231 {
232 ✗ _screenshotCaptureRect = window->Rect();
233 }
234
235 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
236 ✗ if (is_selected) { ImGui::SetItemDefaultFocus(); }
237 ✗ }
238
239 ✗ ImGui::EndCombo();
240 }
241 ✗ ImGui::SameLine();
242 ✗ ImGui::Checkbox("Show Capture rect", &_showScreenshotCaptureRect);
243
244 ✗ ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
245 ✗ ImGui::DragFloat("##Min Capture area x", &_screenshotCaptureRect.Min.x, 1.0F, 0.0F, io.DisplaySize.x - 1);
246 ✗ ImGui::SameLine();
247 ✗ ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().ItemInnerSpacing.x);
248 ✗ ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
249 ✗ ImGui::DragFloat("Min##Capture area y", &_screenshotCaptureRect.Min.y, 1.0F, 0.0F, io.DisplaySize.y - 1);
250
251 ✗ ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
252 ✗ ImGui::DragFloat("##Max Capture area x", &_screenshotCaptureRect.Max.x, 1.0F, _screenshotCaptureRect.Min.x + 1, io.DisplaySize.x);
253 ✗ ImGui::SameLine();
254 ✗ ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().ItemInnerSpacing.x);
255 ✗ ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
256 ✗ ImGui::DragFloat("Max##Capture area y", &_screenshotCaptureRect.Max.y, 1.0F, _screenshotCaptureRect.Min.y + 1, io.DisplaySize.y);
257
258 ✗ if (ImGui::Button("Take screenshot"))
259 {
260 ✗ _screenshotFrameCnt = 1; // Starts taking a screenshot after some frames
261 }
262
263 ✗ ImGui::TreePop();
264 }
265
266 ✗ ImGui::End();
267 }
268
269 ✗ void NAV::gui::windows::CopyFileToClipboard(const char* path)
270 {
271 // NOLINTNEXTLINE
272 ✗ [[maybe_unused]] int exitCode = system(fmt::format("command -v xclip > /dev/null 2>&1 && xclip -selection clipboard -target image/png -i {} && exit 0"
273 "|| command -v wl-copy > /dev/null 2>&1 && wl-copy < {}",
274 path, path)
275 .c_str());
276 ✗ }
277
278 #endif
279