INSTINCT Code Coverage Report


Directory: src/
File: internal/gui/windows/Screenshotter.cpp
Date: 2025-06-02 15:19:59
Exec Total Coverage
Lines: 0 138 0.0%
Functions: 0 2 0.0%
Branches: 0 314 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
38 namespace
39 {
40 ImRect _screenshotNavigateRect;
41 ImRect _screenshotCaptureRect;
42 bool _showScreenshotCaptureRect = false;
43 bool _printScreenshotSaveLocation = true;
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 == 4)
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 _screenshotFrameCnt = 0;
80 }
81
82 return;
83 }
84
85 if (_showScreenshotCaptureRect)
86 {
87 constexpr float thickness = 1.0F;
88 auto rect = _screenshotCaptureRect;
89 rect.Expand(thickness);
90 ImGui::GetForegroundDrawList()->AddRect(rect.Min, rect.Max, ImColor(255, 0, 0),
91 0.0F, ImDrawFlags_None, thickness);
92 }
93
94 if (!ImGui::Begin("Screenshotter", show, ImGuiWindowFlags_AlwaysAutoResize))
95 {
96 ImGui::End();
97 return;
98 }
99
100 const float ITEM_WIDTH = 200.0F * NodeEditorApplication::defaultFontRatio();
101 const float ITEM_WIDTH_HALF = (ITEM_WIDTH - ImGui::GetStyle().ItemInnerSpacing.x) / 2.0F;
102
103 ImGui::TextUnformatted("Plot screenshot style: ");
104 ImGui::SameLine();
105 ImGui::SetNextItemWidth(100.0F * NodeEditorApplication::defaultFontRatio());
106 if (widgets::FileDialogLoad(plotScreenshotImPlotStyleFile, "Plot screenshot config file", ".json", { ".json" },
107 flow::GetConfigPath(), 1, "ImPlotStyleEditorScreenshot"))
108 {
109 LOG_DEBUG("Plot screenshot config file changed to: {}", plotScreenshotImPlotStyleFile);
110 }
111
112 ImGui::Checkbox("Copy screenshots to clipboard", &copyScreenshotsToClipboard);
113
114 if (ImGui::Checkbox("Light mode", &nodeEditorLightMode))
115 {
116 ApplyDarkLightMode(NodeEditorApplication::m_colors);
117 flow::ApplyChanges();
118 }
119 ImGui::SameLine();
120 bool showGridLines = ed::GetStyle().Colors[ed::StyleColor_Grid].w != 0.0F;
121 if (ImGui::Checkbox("Grid lines", &showGridLines))
122 {
123 ed::GetStyle().Colors[ed::StyleColor_Grid].w = showGridLines ? ed::Style().Colors[ed::StyleColor_Grid].w : 0.0F;
124 flow::ApplyChanges();
125 }
126 ImGui::SameLine();
127 bool transparentWindows = ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w != 1.0F;
128 if (ImGui::Checkbox("Transparent windows", &transparentWindows))
129 {
130 ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = transparentWindows ? ImGuiStyle().Colors[ImGuiCol_WindowBg].w : 1.0F;
131 flow::ApplyChanges();
132 }
133
134 if (ImGui::Checkbox("Hide left pane", &NodeEditorApplication::hideLeftPane))
135 {
136 flow::ApplyChanges();
137 }
138 if (!NodeEditorApplication::hideLeftPane)
139 {
140 ImGui::SameLine();
141 ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
142 ImGui::DragFloat("Left pane width", &NodeEditorApplication::leftPaneWidth);
143 }
144 if (ImGui::Checkbox("Hide FPS count", &NodeEditorApplication::hideFPS))
145 {
146 flow::ApplyChanges();
147 }
148 ImGui::SameLine();
149 ImGui::Checkbox("Print save location", &_printScreenshotSaveLocation);
150
151 ImGui::Separator();
152
153 auto* editor = reinterpret_cast<ed::Detail::EditorContext*>(ed::GetCurrentEditor());
154 if (_screenshotNavigateRect.GetArea() == 0.0F) { _screenshotNavigateRect = editor->GetContentBounds(); }
155
156 ImGui::SetNextItemOpen(true, ImGuiCond_Once);
157 if (ImGui::TreeNode("Screenshot navigate area"))
158 {
159 ImGui::TextUnformatted(fmt::format("Current Content Bounds: Min({},{}), Max({},{})",
160 editor->GetContentBounds().Min.x, editor->GetContentBounds().Min.y,
161 editor->GetContentBounds().Max.x, editor->GetContentBounds().Max.y)
162 .c_str());
163 ImGui::TextUnformatted(fmt::format("Current View: Min({},{}), Max({},{})",
164 editor->GetViewRect().Min.x, editor->GetViewRect().Min.y,
165 editor->GetViewRect().Max.x, editor->GetViewRect().Max.y)
166 .c_str());
167
168 if (ImGui::Button("Set area to content")) { _screenshotNavigateRect = editor->GetContentBounds(); }
169 ImGui::SameLine();
170 if (ImGui::Button("Set area to current view"))
171 {
172 _screenshotNavigateRect = editor->GetViewRect();
173
174 auto extend = ImMax(_screenshotNavigateRect.GetWidth(), _screenshotNavigateRect.GetHeight());
175 constexpr float c_NavigationZoomMargin = 0.1F;
176 _screenshotNavigateRect.Expand(-extend * c_NavigationZoomMargin * 0.5F);
177 }
178
179 ImGui::SetNextItemWidth(ITEM_WIDTH);
180 ImGui::DragFloat2("Min##Nav area", &_screenshotNavigateRect.Min.x);
181 ImGui::SetNextItemWidth(ITEM_WIDTH);
182 ImGui::DragFloat2("Max##Nav area", &_screenshotNavigateRect.Max.x);
183
184 if (ImGui::Button("Navigate to area"))
185 {
186 editor->NavigateTo(_screenshotNavigateRect, true);
187 }
188 ImGui::SameLine();
189
190 static float zoomFactor = editor->GetView().Scale;
191 if (ImGui::Button("Zoom"))
192 {
193 auto targetRect = editor->GetCanvas().CalcViewRect(ImGuiEx::CanvasView(editor->GetView().Origin, zoomFactor));
194 editor->NavigateTo(targetRect, true, 0.15F);
195 }
196 ImGui::SameLine();
197 ImGui::SetNextItemWidth(ITEM_WIDTH_HALF / 2.F);
198 ImGui::InputFloat("##Zoom Factor", &zoomFactor);
199 ImGui::SameLine();
200 ImGui::TextUnformatted(fmt::format("Current: {:.3f}", editor->GetView().Scale).c_str());
201
202 ImGui::TreePop();
203 }
204
205 ImGuiIO& io = ImGui::GetIO();
206 if (_screenshotCaptureRect.GetArea() == 0.0F && ImGui::GetFrameCount() > 10) { _screenshotCaptureRect.Max = io.DisplaySize; }
207
208 ImGui::SetNextItemOpen(true, ImGuiCond_Once);
209 if (ImGui::TreeNode("Screenshot capture area"))
210 {
211 if (ImGui::Button("Set to display size"))
212 {
213 _screenshotCaptureRect.Min = ImVec2();
214 _screenshotCaptureRect.Max = io.DisplaySize;
215 }
216 ImGui::SameLine();
217 ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
218 if (ImGui::BeginCombo("##Window list", "Set to window"))
219 {
220 ImVector<ImGuiWindow*>& windows = ImGui::GetCurrentContext()->Windows;
221 for (ImGuiWindow* window : windows)
222 {
223 std::string windowName = window->Name;
224 if (!window->WasActive || windowName.find('#') != std::string::npos || windowName.starts_with("Content")) { continue; }
225 const bool is_selected = window->Rect().Min == _screenshotCaptureRect.Min && window->Rect().Max == _screenshotCaptureRect.Max;
226 if (ImGui::Selectable(window->Name, is_selected))
227 {
228 _screenshotCaptureRect = window->Rect();
229 }
230
231 // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
232 if (is_selected) { ImGui::SetItemDefaultFocus(); }
233 }
234
235 ImGui::EndCombo();
236 }
237 ImGui::SameLine();
238 ImGui::Checkbox("Show Capture rect", &_showScreenshotCaptureRect);
239
240 ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
241 ImGui::DragFloat("##Min Capture area x", &_screenshotCaptureRect.Min.x, 1.0F, 0.0F, io.DisplaySize.x - 1);
242 ImGui::SameLine();
243 ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().ItemInnerSpacing.x);
244 ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
245 ImGui::DragFloat("Min##Capture area y", &_screenshotCaptureRect.Min.y, 1.0F, 0.0F, io.DisplaySize.y - 1);
246
247 ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
248 ImGui::DragFloat("##Max Capture area x", &_screenshotCaptureRect.Max.x, 1.0F, _screenshotCaptureRect.Min.x + 1, io.DisplaySize.x);
249 ImGui::SameLine();
250 ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().ItemInnerSpacing.x);
251 ImGui::SetNextItemWidth(ITEM_WIDTH_HALF);
252 ImGui::DragFloat("Max##Capture area y", &_screenshotCaptureRect.Max.y, 1.0F, _screenshotCaptureRect.Min.y + 1, io.DisplaySize.y);
253
254 if (ImGui::Button("Take screenshot"))
255 {
256 _screenshotFrameCnt = 1; // Starts taking a screenshot after some frames
257 }
258
259 ImGui::TreePop();
260 }
261
262 ImGui::End();
263 }
264
265 void NAV::gui::windows::CopyFileToClipboard(const char* path)
266 {
267 // NOLINTNEXTLINE
268 [[maybe_unused]] int exitCode = system(fmt::format("command -v xclip > /dev/null 2>&1 && xclip -selection clipboard -target image/png -i {} && exit 0"
269 "|| command -v wl-copy > /dev/null 2>&1 && wl-copy < {}",
270 path, path)
271 .c_str());
272 }
273
274 #endif
275