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