35 bool openConfirmPopup =
false;
37 auto stem = [&](std::string_view fullName) {
41 im::Menu(
"Save state", motherBoard !=
nullptr, [&]{
44 std::string_view loadCmd =
"loadstate";
46 if (ImGui::MenuItem(
"Quick load state", loadShortCut.c_str())) {
49 std::string_view saveCmd =
"savestate";
51 if (ImGui::MenuItem(
"Quick save state", saveShortCut.c_str())) {
57 im::Menu(
"Load state ...", existingStates && !existingStates->empty(), [&]{
58 im::Table(
"table", 2, ImGuiTableFlags_BordersInnerV, [&]{
59 if (ImGui::TableNextColumn()) {
60 ImGui::TextUnformatted(
"Select save state"sv);
61 im::ListBox(
"##list", ImVec2(ImGui::GetFontSize() * 20.0f, 240.0f), [&]{
62 for (const auto& name : *existingStates) {
63 if (ImGui::Selectable(name.c_str())) {
64 manager.executeDelayed(makeTclList(
"loadstate", name));
66 if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort) &&
67 (previewImage.name != name)) {
70 previewImage.name = std::string(name);
71 previewImage.texture = gl::Texture(gl::Null{});
73 std::string filename = FileOperations::join(
74 FileOperations::getUserOpenMSXDir(),
75 "savestates", tmpStrCat(name,
".png"));
76 if (FileOperations::exists(filename)) {
79 previewImage.texture = loadTexture(filename, dummy);
85 im::PopupContextItem([&]{
86 if (ImGui::MenuItem(
"delete")) {
87 confirmCmd = makeTclList(
"delete_savestate", name);
88 confirmText = strCat(
"Delete savestate '", name,
"'?");
89 openConfirmPopup = true;
95 if (ImGui::TableNextColumn()) {
96 ImGui::TextUnformatted(
"Preview"sv);
97 ImVec2 size(320, 240);
98 if (previewImage.texture.get()) {
99 ImGui::Image(previewImage.texture.getImGui(), size);
106 saveStateOpen =
im::Menu(
"Save state ...", [&]{
109 saveStateName,
"savestates",
"",
".oms");
112 if (!saveStateOpen) {
115 saveStateName = result->getString();
118 "savestates", result->getString(),
".oms",
true));
123 ImGui::InputText(
"##save-state-name", &saveStateName);
125 if (ImGui::Button(
"Create")) {
126 ImGui::CloseCurrentPopup();
127 confirmCmd =
makeTclList(
"savestate", saveStateName);
129 openConfirmPopup =
true;
130 confirmText =
strCat(
"Overwrite save state with name '", saveStateName,
"'?");
136 if (ImGui::MenuItem(
"Open savestates folder...")) {
145 im::Menu(
"Load replay ...", reverseEnabled, [&]{
149 Names(std::string f, std::string d)
150 : fullName(std::move(f)), displayName(std::move(d)) {}
151 std::string fullName;
152 std::string displayName;
154 std::vector<Names> names;
156 const auto& path : context.getPaths()) {
157 foreach_file(path, [&](
const std::string& fullName, std::string_view name) {
159 name.remove_suffix(ReverseManager::REPLAY_EXTENSION.size());
160 names.emplace_back(fullName, std::string(name));
165 for (
const auto& [fullName_, displayName_] : names) {
166 const auto& fullName = fullName_;
167 const auto& displayName = displayName_;
168 if (ImGui::Selectable(displayName.c_str())) {
172 if (ImGui::MenuItem(
"delete")) {
173 confirmCmd =
makeTclList(
"file",
"delete", fullName);
174 confirmText =
strCat(
"Delete replay '", displayName,
"'?");
175 openConfirmPopup =
true;
181 saveReplayOpen =
im::Menu(
"Save replay ...", reverseEnabled, [&]{
187 if (!saveReplayOpen) {
190 saveReplayName = result->getString();
198 ImGui::InputText(
"##save-replay-name", &saveReplayName);
200 if (ImGui::Button(
"Create")) {
201 ImGui::CloseCurrentPopup();
203 confirmCmd =
makeTclList(
"reverse",
"savereplay", saveReplayName);
205 openConfirmPopup =
true;
206 confirmText =
strCat(
"Overwrite replay with name '", saveReplayName,
"'?");
212 if (ImGui::MenuItem(
"Open replays folder...")) {
215 im::Menu(
"Reverse/replay settings", [&]{
216 if (ImGui::MenuItem(
"Enable reverse/replay",
nullptr, &reverseEnabled)) {
219 simpleToolTip(
"Enable/disable reverse/replay right now, for the currently running machine");
222 bool autoEnableReverse = autoEnableReverseSetting->getBoolean();
223 if (ImGui::MenuItem(
"Auto enable reverse",
nullptr, &autoEnableReverse)) {
224 autoEnableReverseSetting->setBoolean(autoEnableReverse);
229 ImGui::MenuItem(
"Show reverse bar",
nullptr, &
showReverseBar, reverseEnabled);
233 const auto popupTitle =
"Confirm##reverse";
234 if (openConfirmPopup) {
235 ImGui::OpenPopup(popupTitle);
237 im::PopupModal(popupTitle,
nullptr, ImGuiWindowFlags_AlwaysAutoResize, [&]{
241 if (ImGui::Button(
"Ok")) {
242 manager.executeDelayed(confirmCmd);
246 close |= ImGui::Button(
"Cancel");
248 ImGui::CloseCurrentPopup();
249 confirmCmd = TclObject();
256 if (!showReverseBar)
return;
257 if (!motherBoard)
return;
259 if (!reverseManager.isCollecting())
return;
261 const auto& style = ImGui::GetStyle();
262 auto textHeight = ImGui::GetTextLineHeight();
263 auto windowHeight = style.WindowPadding.y + 2.0f * textHeight + style.WindowPadding.y;
264 if (!reverseHideTitle) {
265 windowHeight += style.FramePadding.y + textHeight + style.FramePadding.y;
267 ImGui::SetNextWindowSizeConstraints(ImVec2(250, windowHeight), ImVec2(FLT_MAX, windowHeight));
270 const auto* viewPort = ImGui::GetMainViewport();
272 ImGuiCond_FirstUseEver,
275 int flags = reverseHideTitle ? ImGuiWindowFlags_NoTitleBar |
276 ImGuiWindowFlags_NoResize |
277 ImGuiWindowFlags_NoScrollbar |
278 ImGuiWindowFlags_NoScrollWithMouse |
279 ImGuiWindowFlags_NoCollapse |
280 ImGuiWindowFlags_NoBackground |
281 ImGuiWindowFlags_NoFocusOnAppearing |
282 (reverseAllowMove ? 0 : ImGuiWindowFlags_NoMove)
285 im::Window(
"Reverse bar", &showReverseBar, flags, [&]{
286 bool isOnMainViewPort = adjust.post();
287 auto b = reverseManager.getBegin();
288 auto e = reverseManager.getEnd();
289 auto c = reverseManager.getCurrent();
290 auto snapshots = reverseManager.getSnapshotTimes();
292 auto totalLength = e - b;
293 auto playLength = c - b;
294 auto recipLength = (totalLength != 0.0) ? (1.0 / totalLength) : 0.0;
295 auto fraction = narrow_cast<float>(playLength * recipLength);
297 gl::vec2 pos = ImGui::GetCursorScreenPos();
298 gl::vec2 availableSize = ImGui::GetContentRegionAvail();
299 gl::vec2 outerSize(availableSize.x, 2.0f * textHeight);
301 gl::vec2 outerBottomRight = outerTopLeft + outerSize;
305 gl::vec2 innerBottomRight = innerTopLeft + innerSize;
306 gl::vec2 barBottomRight = innerTopLeft +
gl::vec2(innerSize.x * fraction, innerSize.y);
308 gl::vec2 middleTopLeft (barBottomRight.x - 2.0f, innerTopLeft.y);
309 gl::vec2 middleBottomRight(barBottomRight.x + 2.0f, innerBottomRight.y);
311 const auto& io = ImGui::GetIO();
312 bool hovered = ImGui::IsWindowHovered();
313 bool replaying = reverseManager.isReplaying();
314 if (!reverseHideTitle || !reverseFadeOut || replaying ||
315 ImGui::IsWindowDocked() || !isOnMainViewPort) {
318 auto target = hovered ? 1.0f : 0.0f;
319 auto period = hovered ? 0.5f : 5.0f;
323 return ImGui::ColorConvertFloat4ToU32(col * reverseAlpha);
326 auto* drawList = ImGui::GetWindowDrawList();
327 drawList->AddRectFilled(innerTopLeft, innerBottomRight, color(
gl::vec4(0.0f, 0.0f, 0.0f, 0.5f)));
329 for (
double s : snapshots) {
330 float x = narrow_cast<float>((s - b) * recipLength) * innerSize.x;
331 drawList->AddLine(
gl::vec2(innerTopLeft.x + x, innerTopLeft.y),
332 gl::vec2(innerTopLeft.x + x, innerBottomRight.y),
333 color(
gl::vec4(0.25f, 0.25f, 0.25f, 1.00f)));
336 static constexpr std::array barColors = {
337 std::array{
gl::vec4(0.00f, 1.00f, 0.27f, 0.63f),
gl::vec4(0.00f, 0.73f, 0.13f, 0.63f),
338 gl::vec4(0.07f, 0.80f, 0.80f, 0.63f),
gl::vec4(0.00f, 0.87f, 0.20f, 0.63f)},
339 std::array{
gl::vec4(0.00f, 0.27f, 1.00f, 0.63f),
gl::vec4(0.00f, 0.13f, 0.73f, 0.63f),
340 gl::vec4(0.07f, 0.80f, 0.80f, 0.63f),
gl::vec4(0.00f, 0.20f, 0.87f, 0.63f)},
341 std::array{
gl::vec4(1.00f, 0.27f, 0.00f, 0.63f),
gl::vec4(0.87f, 0.20f, 0.00f, 0.63f),
342 gl::vec4(0.80f, 0.80f, 0.07f, 0.63f),
gl::vec4(0.73f, 0.13f, 0.00f, 0.63f)},
344 int barColorsIndex = replaying ? (reverseManager.isViewOnlyMode() ? 0 : 1)
346 const auto& barColor = barColors[barColorsIndex];
347 drawList->AddRectFilledMultiColor(
348 innerTopLeft, barBottomRight,
349 color(barColor[0]), color(barColor[1]), color(barColor[2]), color(barColor[3]));
351 drawList->AddRectFilled(middleTopLeft, middleBottomRight, color(
gl::vec4(1.0f, 0.5f, 0.0f, 0.75f)));
353 outerTopLeft, outerBottomRight, color(
gl::vec4(1.0f)), 0.0f, 0, 2.0f);
357 gl::vec2 cursor = ImGui::GetCursorPos();
358 ImGui::SetCursorPos(cursor +
gl::vec2(std::max(0.0f, 0.5f * (outerSize.x - timeSize)), textHeight * 0.5f));
359 ImGui::TextColored(
gl::vec4(1.0f) * reverseAlpha,
"%s", timeStr.c_str());
361 if (hovered && ImGui::IsMouseHoveringRect(outerTopLeft, outerBottomRight)) {
362 float ratio = (io.MousePos.x - pos.x) / outerSize.x;
363 auto timeOffset = totalLength * double(ratio);
367 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
368 manager.executeDelayed(
makeTclList(
"reverse",
"goto", b + timeOffset));
372 ImGui::SetCursorPos(cursor);
373 ImGui::Dummy(availableSize);
375 ImGui::Checkbox(
"Hide title", &reverseHideTitle);
378 ImGui::Checkbox(
"Fade out", &reverseFadeOut);
379 ImGui::Checkbox(
"Allow move", &reverseAllowMove);