40 bool openConfirmPopup =
false;
42 auto stem = [&](std::string_view fullName) {
46 im::Menu(
"Save state", motherBoard !=
nullptr, [&]{
49 std::string_view loadCmd =
"loadstate";
51 if (ImGui::MenuItem(
"Quick load state", loadShortCut.c_str())) {
54 std::string_view saveCmd =
"savestate";
56 if (ImGui::MenuItem(
"Quick save state", saveShortCut.c_str())) {
62 im::Menu(
"Load state ...", existingStates && !existingStates->empty(), [&]{
63 im::Table(
"table", 2, ImGuiTableFlags_BordersInnerV, [&]{
64 if (ImGui::TableNextColumn()) {
65 ImGui::TextUnformatted(
"Select save state"sv);
66 im::ListBox(
"##list", ImVec2(ImGui::GetFontSize() * 20.0f, 240.0f), [&]{
67 for (const auto& name : *existingStates) {
68 if (ImGui::Selectable(name.c_str())) {
69 manager.executeDelayed(makeTclList(
"loadstate", name));
71 if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort) &&
72 (previewImage.name != name)) {
75 previewImage.name = std::string(name);
76 previewImage.texture = gl::Texture(gl::Null{});
78 std::string filename = FileOperations::join(
79 FileOperations::getUserOpenMSXDir(),
80 "savestates", tmpStrCat(name,
".png"));
81 if (FileOperations::exists(filename)) {
84 previewImage.texture = loadTexture(filename, dummy);
90 im::PopupContextItem([&]{
91 if (ImGui::MenuItem(
"delete")) {
92 confirmCmd = makeTclList(
"delete_savestate", name);
93 confirmText = strCat(
"Delete savestate '", name,
"'?");
94 openConfirmPopup = true;
100 if (ImGui::TableNextColumn()) {
101 ImGui::TextUnformatted(
"Preview"sv);
102 ImVec2 size(320, 240);
103 if (previewImage.texture.get()) {
104 ImGui::Image(previewImage.texture.getImGui(), size);
111 saveStateOpen =
im::Menu(
"Save state ...", [&]{
114 saveStateName,
"savestates",
"",
".oms");
117 if (!saveStateOpen) {
120 saveStateName = result->getString();
123 "savestates", result->getString(),
".oms",
true));
128 ImGui::InputText(
"##save-state-name", &saveStateName);
130 if (ImGui::Button(
"Create")) {
131 ImGui::CloseCurrentPopup();
132 confirmCmd =
makeTclList(
"savestate", saveStateName);
134 openConfirmPopup =
true;
135 confirmText =
strCat(
"Overwrite save state with name '", saveStateName,
"'?");
141 if (ImGui::MenuItem(
"Open savestates folder...")) {
150 im::Menu(
"Load replay ...", reverseEnabled, [&]{
154 Names(std::string f, std::string d)
155 : fullName(std::move(f)), displayName(std::move(d)) {}
156 std::string fullName;
157 std::string displayName;
159 std::vector<Names> names;
161 const auto& path : context.getPaths()) {
162 foreach_file(path, [&](
const std::string& fullName, std::string_view name) {
164 name.remove_suffix(ReverseManager::REPLAY_EXTENSION.size());
165 names.emplace_back(fullName, std::string(name));
170 for (
const auto& [fullName_, displayName_] : names) {
171 const auto& fullName = fullName_;
172 const auto& displayName = displayName_;
173 if (ImGui::Selectable(displayName.c_str())) {
177 if (ImGui::MenuItem(
"delete")) {
178 confirmCmd =
makeTclList(
"file",
"delete", fullName);
179 confirmText =
strCat(
"Delete replay '", displayName,
"'?");
180 openConfirmPopup =
true;
186 saveReplayOpen =
im::Menu(
"Save replay ...", reverseEnabled, [&]{
192 if (!saveReplayOpen) {
195 saveReplayName = result->getString();
203 ImGui::InputText(
"##save-replay-name", &saveReplayName);
205 if (ImGui::Button(
"Create")) {
206 ImGui::CloseCurrentPopup();
208 confirmCmd =
makeTclList(
"reverse",
"savereplay", saveReplayName);
210 openConfirmPopup =
true;
211 confirmText =
strCat(
"Overwrite replay with name '", saveReplayName,
"'?");
217 if (ImGui::MenuItem(
"Open replays folder...")) {
220 im::Menu(
"Reverse/replay settings", [&]{
221 if (ImGui::MenuItem(
"Enable reverse/replay",
nullptr, &reverseEnabled)) {
224 simpleToolTip(
"Enable/disable reverse/replay right now, for the currently running machine");
227 bool autoEnableReverse = autoEnableReverseSetting->getBoolean();
228 if (ImGui::MenuItem(
"Auto enable reverse",
nullptr, &autoEnableReverse)) {
229 autoEnableReverseSetting->setBoolean(autoEnableReverse);
234 ImGui::MenuItem(
"Show reverse bar",
nullptr, &
showReverseBar, reverseEnabled);
238 const auto popupTitle =
"Confirm##reverse";
239 if (openConfirmPopup) {
240 ImGui::OpenPopup(popupTitle);
242 im::PopupModal(popupTitle,
nullptr, ImGuiWindowFlags_AlwaysAutoResize, [&]{
246 if (ImGui::Button(
"Ok")) {
247 manager.executeDelayed(confirmCmd);
251 close |= ImGui::Button(
"Cancel");
253 ImGui::CloseCurrentPopup();
254 confirmCmd = TclObject();
261 if (!showReverseBar)
return;
262 if (!motherBoard)
return;
264 if (!reverseManager.isCollecting())
return;
266 const auto& style = ImGui::GetStyle();
267 auto textHeight = ImGui::GetTextLineHeight();
268 auto windowHeight = style.WindowPadding.y + 2.0f * textHeight + style.WindowPadding.y;
269 if (!reverseHideTitle) {
270 windowHeight += style.FramePadding.y + textHeight + style.FramePadding.y;
272 ImGui::SetNextWindowSizeConstraints(ImVec2(250, windowHeight), ImVec2(FLT_MAX, windowHeight));
275 const auto* viewPort = ImGui::GetMainViewport();
277 ImGuiCond_FirstUseEver,
280 int flags = reverseHideTitle ? ImGuiWindowFlags_NoTitleBar |
281 ImGuiWindowFlags_NoResize |
282 ImGuiWindowFlags_NoScrollbar |
283 ImGuiWindowFlags_NoScrollWithMouse |
284 ImGuiWindowFlags_NoCollapse |
285 ImGuiWindowFlags_NoBackground |
286 ImGuiWindowFlags_NoFocusOnAppearing |
287 ImGuiWindowFlags_NoNav |
288 (reverseAllowMove ? 0 : ImGuiWindowFlags_NoMove)
291 im::Window(
"Reverse bar", &showReverseBar, flags, [&]{
292 bool isOnMainViewPort = adjust.post();
293 auto b = reverseManager.getBegin();
294 auto e = reverseManager.getEnd();
295 auto c = reverseManager.getCurrent();
297 auto totalLength = e - b;
298 auto playLength = c - b;
299 auto recipLength = (totalLength != 0.0) ? (1.0 / totalLength) : 0.0;
300 auto fraction = narrow_cast<float>(playLength * recipLength);
302 gl::vec2 pos = ImGui::GetCursorScreenPos();
303 gl::vec2 availableSize = ImGui::GetContentRegionAvail();
304 gl::vec2 outerSize(availableSize.x, 2.0f * textHeight);
306 gl::vec2 outerBottomRight = outerTopLeft + outerSize;
308 const auto& io = ImGui::GetIO();
309 bool hovered = ImGui::IsWindowHovered();
310 bool replaying = reverseManager.isReplaying();
311 if (!reverseHideTitle || !reverseFadeOut || replaying ||
312 ImGui::IsWindowDocked() || !isOnMainViewPort) {
315 auto target = hovered ? 1.0f : 0.0f;
316 auto period = hovered ? 0.5f : 5.0f;
319 if (reverseAlpha != 0.0f) {
322 gl::vec2 innerBottomRight = innerTopLeft + innerSize;
323 gl::vec2 barBottomRight = innerTopLeft +
gl::vec2(innerSize.x * fraction, innerSize.y);
325 gl::vec2 middleTopLeft (barBottomRight.x - 2.0f, innerTopLeft.y);
326 gl::vec2 middleBottomRight(barBottomRight.x + 2.0f, innerBottomRight.y);
329 return ImGui::ColorConvertFloat4ToU32(col * reverseAlpha);
332 auto* drawList = ImGui::GetWindowDrawList();
333 drawList->AddRectFilled(innerTopLeft, innerBottomRight, color(
gl::vec4(0.0f, 0.0f, 0.0f, 0.5f)));
335 for (
double s : reverseManager.getSnapshotTimes()) {
336 float x = narrow_cast<float>((s - b) * recipLength) * innerSize.x;
337 drawList->AddLine(
gl::vec2(innerTopLeft.x + x, innerTopLeft.y),
338 gl::vec2(innerTopLeft.x + x, innerBottomRight.y),
339 color(
gl::vec4(0.25f, 0.25f, 0.25f, 1.00f)));
342 static constexpr std::array barColors = {
343 std::array{
gl::vec4(0.00f, 1.00f, 0.27f, 0.63f),
gl::vec4(0.00f, 0.73f, 0.13f, 0.63f),
344 gl::vec4(0.07f, 0.80f, 0.80f, 0.63f),
gl::vec4(0.00f, 0.87f, 0.20f, 0.63f)},
345 std::array{
gl::vec4(0.00f, 0.27f, 1.00f, 0.63f),
gl::vec4(0.00f, 0.13f, 0.73f, 0.63f),
346 gl::vec4(0.07f, 0.80f, 0.80f, 0.63f),
gl::vec4(0.00f, 0.20f, 0.87f, 0.63f)},
347 std::array{
gl::vec4(1.00f, 0.27f, 0.00f, 0.63f),
gl::vec4(0.87f, 0.20f, 0.00f, 0.63f),
348 gl::vec4(0.80f, 0.80f, 0.07f, 0.63f),
gl::vec4(0.73f, 0.13f, 0.00f, 0.63f)},
350 int barColorsIndex = replaying ? (reverseManager.isViewOnlyMode() ? 0 : 1)
352 const auto& barColor = barColors[barColorsIndex];
353 drawList->AddRectFilledMultiColor(
354 innerTopLeft, barBottomRight,
355 color(barColor[0]), color(barColor[1]), color(barColor[2]), color(barColor[3]));
357 drawList->AddRectFilled(middleTopLeft, middleBottomRight, color(
gl::vec4(1.0f, 0.5f, 0.0f, 0.75f)));
359 outerTopLeft, outerBottomRight, color(
gl::vec4(1.0f)), 0.0f, 0, 2.0f);
363 gl::vec2 cursor = ImGui::GetCursorPos();
364 ImGui::SetCursorPos(cursor +
gl::vec2(std::max(0.0f, 0.5f * (outerSize.x - timeSize)), textHeight * 0.5f));
365 ImGui::TextColored(
gl::vec4(1.0f) * reverseAlpha,
"%s", timeStr.c_str());
366 ImGui::SetCursorPos(cursor);
369 if (hovered && ImGui::IsMouseHoveringRect(outerTopLeft, outerBottomRight)) {
370 float ratio = (io.MousePos.x - pos.x) / outerSize.x;
371 auto timeOffset = totalLength * double(ratio);
375 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
376 manager.executeDelayed(
makeTclList(
"reverse",
"goto", b + timeOffset));
380 ImGui::Dummy(availableSize);
382 ImGui::Checkbox(
"Hide title", &reverseHideTitle);
385 ImGui::Checkbox(
"Fade out", &reverseFadeOut);
386 ImGui::Checkbox(
"Allow move", &reverseAllowMove);
391 if (reverseHideTitle && ImGui::IsWindowFocused()) {
392 ImGui::SetWindowFocus(
nullptr);