INSTINCT Code Coverage Report


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