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", ©ScreenshotsToClipboard); |
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 |
|
|
|