openMSX
ImGuiReverseBar.cc
Go to the documentation of this file.
1#include "ImGuiReverseBar.hh"
2
3#include "ImGuiCpp.hh"
4#include "ImGuiManager.hh"
5#include "ImGuiUtils.hh"
6
7#include "FileContext.hh"
8#include "FileOperations.hh"
9#include "GLImage.hh"
10#include "MSXMotherBoard.hh"
11#include "ReverseManager.hh"
13#include "CliComm.hh"
14
15#include "foreach_file.hh"
16
17#include <imgui.h>
18#include <imgui_stdlib.h>
19
20using namespace std::literals;
21
22namespace openmsx {
23
24
25static constexpr std::string_view STATE_EXTENSION = ".oms";
26static constexpr std::string_view STATE_DIR = "savestates";
27
28void ImGuiReverseBar::save(ImGuiTextBuffer& buf)
29{
30 savePersistent(buf, *this, persistentElements);
31 adjust.save(buf);
32}
33
34void ImGuiReverseBar::loadLine(std::string_view name, zstring_view value)
35{
36 if (loadOnePersistent(name, value, *this, persistentElements)) {
37 // already handled
38 } else if (adjust.loadLine(name, value)) {
39 // already handled
40 }
41}
42
44{
45 bool openConfirmPopup = false;
46
47 auto stem = [&](std::string_view fullName) {
49 };
50
51 im::Menu("Save state", motherBoard != nullptr, [&]{
52 const auto& hotKey = manager.getReactor().getHotKey();
53
54 std::string_view loadCmd = "loadstate";
55 auto loadShortCut = getShortCutForCommand(hotKey, loadCmd);
56 if (ImGui::MenuItem("Quick load state", loadShortCut.c_str())) {
58 }
59 std::string_view saveCmd = "savestate";
60 auto saveShortCut = getShortCutForCommand(hotKey, saveCmd);
61 if (ImGui::MenuItem("Quick save state", saveShortCut.c_str())) {
63 }
64 ImGui::Separator();
65
66 auto formatFileTimeFull = [](std::time_t fileTime) {
67 // Convert time_t to local time (broken-down time in the local time zone)
68 std::tm* local_time = std::localtime(&fileTime);
69
70 // Get the local time in human-readable format
71 std::stringstream ss;
72 ss << std::put_time(local_time, "%F %T");
73 return ss.str();
74 };
75
76 auto formatFileAbbreviated = [](std::time_t fileTime) {
77 // Convert time_t to local time (broken-down time in the local time zone)
78 std::tm local_time = *std::localtime(&fileTime);
79
80 std::time_t t_now = std::time(nullptr); // get time now
81 std::tm now = *std::localtime(&t_now);
82
83 const std::string format = ((now.tm_mday == local_time.tm_mday) &&
84 (now.tm_mon == local_time.tm_mon ) &&
85 (now.tm_year == local_time.tm_year)) ? "%T" : "%F";
86
87 // Get the local time in human-readable format
88 std::stringstream ss;
89 ss << std::put_time(&local_time, format.c_str());
90 return ss.str();
91 };
92
93 auto scanDirectory = [](std::string_view dir, std::string_view extension, Info& info) {
94 // As we still want to support gcc-11 (and not require 13 yet), we have to use this method
95 // as workaround instead of using more modern std::chrono and std::format stuff in all time
96 // calculations and formatting.
97 auto fileTimeToTimeT = [](std::filesystem::file_time_type fileTime) {
98 // Convert file_time_type to system_clock::time_point (the standard clock since epoch)
99 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
100 fileTime - std::filesystem::file_time_type::clock::now() +
101 std::chrono::system_clock::now());
102
103 // Convert system_clock::time_point to time_t (time since Unix epoch in seconds)
104 return std::chrono::system_clock::to_time_t(sctp);
105 };
106
107 // on each re-open of this menu, we recreate the list of entries
108 info.entries.clear();
109 info.entriesChanged = true;
110 for (auto context = userDataFileContext(dir);
111 const auto& path : context.getPaths()) {
112 foreach_file(path, [&](const std::string& fullName, std::string_view name) {
113 if (name.ends_with(extension)) {
114 name.remove_suffix(extension.size());
115 std::filesystem::file_time_type ftime = std::filesystem::last_write_time(fullName);
116 info.entries.emplace_back(fullName, std::string(name), fileTimeToTimeT(ftime));
117 }
118 });
119 }
120 };
121
122 int selectionTableFlags = ImGuiTableFlags_RowBg |
123 ImGuiTableFlags_BordersV |
124 ImGuiTableFlags_BordersOuter |
125 ImGuiTableFlags_Resizable |
126 ImGuiTableFlags_Sortable |
127 ImGuiTableFlags_Hideable |
128 ImGuiTableFlags_Reorderable |
129 ImGuiTableFlags_ContextMenuInBody |
130 ImGuiTableFlags_ScrollY |
131 ImGuiTableFlags_SizingStretchProp;
132
133 auto setAndSortColumns = [](bool& namesChanged, std::vector<Info::Entry>& names) {
134 ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
135 ImGui::TableSetupColumn("Name");
136 ImGui::TableSetupColumn("Date/time", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthFixed);
137 ImGui::TableHeadersRow();
138 // check sort order
139 auto* sortSpecs = ImGui::TableGetSortSpecs();
140 if (sortSpecs->SpecsDirty || namesChanged) {
141 sortSpecs->SpecsDirty = false;
142 namesChanged = false;
143 assert(sortSpecs->SpecsCount == 1);
144 assert(sortSpecs->Specs);
145 assert(sortSpecs->Specs->SortOrder == 0);
146
147 switch (sortSpecs->Specs->ColumnIndex) {
148 case 0: // name
150 break;
151 case 1: // time
152 sortUpDown_T(names, sortSpecs, &Info::Entry::ftime);
153 break;
154 default:
156 }
157 }
158 };
159
160 saveStateInfo.submenuOpen = im::Menu("Load state ...", [&]{
161 if (!saveStateInfo.submenuOpen) {
162 scanDirectory(STATE_DIR, STATE_EXTENSION, saveStateInfo);
163 }
164 if (saveStateInfo.entries.empty()) {
165 ImGui::TextUnformatted("No save states found"sv);
166 } else {
167 im::Table("table", 2, ImGuiTableFlags_BordersInnerV, [&]{
168 if (ImGui::TableNextColumn()) {
169 im::Table("##select-savestate", 2, selectionTableFlags, ImVec2(ImGui::GetFontSize() * 25.0f, 240.0f), [&]{
170 setAndSortColumns(saveStateInfo.entriesChanged, saveStateInfo.entries);
171 for (const auto& [fullName, name_, ftime_] : saveStateInfo.entries) {
172 auto ftime = ftime_; // pre-clang-16 workaround
173 const auto& name = name_; // clang workaround
174 if (ImGui::TableNextColumn()) {
175 if (ImGui::Selectable(name.c_str())) {
176 manager.executeDelayed(makeTclList("loadstate", name));
177 }
178 simpleToolTip(name);
179 if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort) &&
180 (previewImage.name != name)) {
181 // record name, but (so far) without image
182 // this prevents that on a missing image, we don't continue retrying
183 previewImage.name = std::string(name);
184 previewImage.texture = gl::Texture(gl::Null{});
185 std::string_view shortFullName = fullName;
186 shortFullName.remove_suffix(STATE_EXTENSION.size());
187 std::string filename = strCat(shortFullName, ".png");
188 if (FileOperations::exists(filename)) {
189 try {
190 gl::ivec2 dummy;
191 previewImage.texture = loadTexture(filename, dummy);
192 } catch (...) {
193 // ignore
194 }
195 }
196 }
197 im::PopupContextItem([&]{
198 if (ImGui::MenuItem("delete")) {
199 confirmCmd = makeTclList("delete_savestate", name);
200 confirmText = strCat("Delete savestate '", name, "'?");
201 openConfirmPopup = true;
202 }
203 });
204 }
205 if (ImGui::TableNextColumn()) {
206 ImGui::TextUnformatted(formatFileAbbreviated(ftime));
207 simpleToolTip([&] { return formatFileTimeFull(ftime); });
208 }
209 }
210 });
211 }
212 if (ImGui::TableNextColumn()) {
213 gl::vec2 size(320, 240);
214 if (previewImage.texture.get()) {
215 ImGui::Image(previewImage.texture.getImGui(), size);
216 } else {
217 gl::vec2 pos = ImGui::GetCursorPos();
218 ImGui::Dummy(size);
219 auto text = "No preview available..."sv;
220 gl::vec2 textSize = ImGui::CalcTextSize(text);
221 ImGui::SetCursorPos(pos + 0.5f*(size - textSize));
222 ImGui::TextUnformatted(text);
223 }
224 }
225 });
226 }
227 });
228 saveStateOpen = im::Menu("Save state ...", [&]{
229 auto exists = [&]{
231 saveStateName, STATE_DIR, "", STATE_EXTENSION);
232 return FileOperations::exists(filename);
233 };
234 if (!saveStateOpen) {
235 // on each re-open of this menu, create a suggestion for a name
236 if (auto result = manager.execute(makeTclList("guess_title", "savestate"))) {
237 saveStateName = result->getString();
238 if (exists()) {
239 saveStateName = stem(FileOperations::getNextNumberedFileName(
240 STATE_DIR, result->getString(), STATE_EXTENSION, true));
241 }
242 }
243 }
244 ImGui::TextUnformatted("Enter name:"sv);
245 ImGui::InputText("##save-state-name", &saveStateName);
246 ImGui::SameLine();
247 if (ImGui::Button("Create")) {
248 ImGui::CloseCurrentPopup();
249 confirmCmd = makeTclList("savestate", saveStateName);
250 if (exists()) {
251 openConfirmPopup = true;
252 confirmText = strCat("Overwrite save state with name '", saveStateName, "'?");
253 } else {
254 manager.executeDelayed(confirmCmd,
255 [&] (const TclObject& result) { manager.getCliComm().printInfo(strCat("State saved to ", result.getString())); },
256 [&] (const std::string& error) { manager.getCliComm().printError(error); }
257 );
258 }
259 }
260 });
261 if (ImGui::MenuItem("Open savestates folder...")) {
262 SDL_OpenURL(strCat("file://", FileOperations::getUserOpenMSXDir(), '/', STATE_DIR).c_str());
263 }
264
265 ImGui::Separator();
266
267 const auto& reverseManager = motherBoard->getReverseManager();
268 bool reverseEnabled = reverseManager.isCollecting();
269
270 replayInfo.submenuOpen = im::Menu("Load replay ...", reverseEnabled, [&]{
271 if (!replayInfo.submenuOpen) {
272 scanDirectory(ReverseManager::REPLAY_DIR, ReverseManager::REPLAY_EXTENSION, replayInfo);
273 }
274 if (replayInfo.entries.empty()) {
275 ImGui::TextUnformatted("No replays found"sv);
276 } else {
277 im::Table("##select-replay", 2, selectionTableFlags, ImVec2(ImGui::GetFontSize() * 25.0f, 240.0f), [&]{
278 setAndSortColumns(replayInfo.entriesChanged, replayInfo.entries);
279 for (const auto& [fullName_, displayName_, ftime_] : replayInfo.entries) {
280 auto ftime = ftime_; // pre-clang-16 workaround
281 const auto& fullName = fullName_; // clang workaround
282 if (ImGui::TableNextColumn()) {
283 const auto& displayName = displayName_; // clang workaround
284 if (ImGui::Selectable(displayName.c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) {
285 manager.executeDelayed(makeTclList("reverse", "loadreplay", fullName));
286 }
287 simpleToolTip(displayName);
288 im::PopupContextItem([&]{
289 if (ImGui::MenuItem("delete")) {
290 confirmCmd = makeTclList("file", "delete", fullName);
291 confirmText = strCat("Delete replay '", displayName, "'?");
292 openConfirmPopup = true;
293 }
294 });
295 }
296 if (ImGui::TableNextColumn()) {
297 ImGui::TextUnformatted(formatFileAbbreviated(ftime));
298 simpleToolTip([&] { return formatFileTimeFull(ftime); });
299 }
300 }});
301 }
302 });
303 saveReplayOpen = im::Menu("Save replay ...", reverseEnabled, [&]{
304 auto exists = [&]{
307 return FileOperations::exists(filename);
308 };
309 if (!saveReplayOpen) {
310 // on each re-open of this menu, create a suggestion for a name
311 if (auto result = manager.execute(makeTclList("guess_title", "replay"))) {
312 saveReplayName = result->getString();
313 if (exists()) {
314 saveReplayName = stem(FileOperations::getNextNumberedFileName(
316 }
317 }
318 }
319 ImGui::TextUnformatted("Enter name:"sv);
320 ImGui::InputText("##save-replay-name", &saveReplayName);
321 ImGui::SameLine();
322 if (ImGui::Button("Create")) {
323 ImGui::CloseCurrentPopup();
324
325 confirmCmd = makeTclList("reverse", "savereplay", saveReplayName);
326 if (exists()) {
327 openConfirmPopup = true;
328 confirmText = strCat("Overwrite replay with name '", saveReplayName, "'?");
329 } else {
330 manager.executeDelayed(confirmCmd,
331 [&] (const TclObject& result) { manager.getCliComm().printInfo(strCat("Replay saved to ", result.getString())); },
332 [&] (const std::string& error) { manager.getCliComm().printError(error); }
333 );
334 }
335 }
336 });
337 if (ImGui::MenuItem("Open replays folder...")) {
338 SDL_OpenURL(strCat("file://", FileOperations::getUserOpenMSXDir(), '/', ReverseManager::REPLAY_DIR).c_str());
339 }
340 im::Menu("Reverse/replay settings", [&]{
341 if (ImGui::MenuItem("Enable reverse/replay", nullptr, &reverseEnabled)) {
342 manager.executeDelayed(makeTclList("reverse", reverseEnabled ? "start" : "stop"));
343 }
344 simpleToolTip("Enable/disable reverse/replay right now, for the currently running machine");
345 if (auto* autoEnableReverseSetting = dynamic_cast<BooleanSetting*>(manager.getReactor().getGlobalCommandController().getSettingsManager().findSetting("auto_enable_reverse"))) {
346
347 bool autoEnableReverse = autoEnableReverseSetting->getBoolean();
348 if (ImGui::MenuItem("Auto enable reverse", nullptr, &autoEnableReverse)) {
349 autoEnableReverseSetting->setBoolean(autoEnableReverse);
350 }
351 simpleToolTip(autoEnableReverseSetting->getDescription());
352 }
353
354 ImGui::MenuItem("Show reverse bar", nullptr, &showReverseBar, reverseEnabled);
355 });
356 });
357
358 const auto popupTitle = "Confirm##reverse";
359 if (openConfirmPopup) {
360 ImGui::OpenPopup(popupTitle);
361 }
362 im::PopupModal(popupTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize, [&]{
363 ImGui::TextUnformatted(confirmText);
364
365 bool close = false;
366 if (ImGui::Button("Ok")) {
367 manager.executeDelayed(confirmCmd);
368 close = true;
369 }
370 ImGui::SameLine();
371 close |= ImGui::Button("Cancel");
372 if (close) {
373 ImGui::CloseCurrentPopup();
374 confirmCmd = TclObject();
375 }
376 });
377}
378
379void ImGuiReverseBar::paint(MSXMotherBoard* motherBoard)
380{
381 if (!showReverseBar) return;
382 if (!motherBoard) return;
383 const auto& reverseManager = motherBoard->getReverseManager();
384 if (!reverseManager.isCollecting()) return;
385
386 const auto& style = ImGui::GetStyle();
387 auto textHeight = ImGui::GetTextLineHeight();
388 auto windowHeight = style.WindowPadding.y + 2.0f * textHeight + style.WindowPadding.y;
389 if (!reverseHideTitle) {
390 windowHeight += style.FramePadding.y + textHeight + style.FramePadding.y;
391 }
392 ImGui::SetNextWindowSizeConstraints(ImVec2(250, windowHeight), ImVec2(FLT_MAX, windowHeight));
393
394 // default placement: bottom right
395 const auto* viewPort = ImGui::GetMainViewport();
396 ImGui::SetNextWindowPos(gl::vec2(viewPort->Pos) + gl::vec2(viewPort->WorkSize) - gl::vec2(10.0f),
397 ImGuiCond_FirstUseEver,
398 {1.0f, 1.0f}); // pivot = bottom-right
399
400 int flags = reverseHideTitle ? ImGuiWindowFlags_NoTitleBar |
401 ImGuiWindowFlags_NoResize |
402 ImGuiWindowFlags_NoScrollbar |
403 ImGuiWindowFlags_NoScrollWithMouse |
404 ImGuiWindowFlags_NoCollapse |
405 ImGuiWindowFlags_NoBackground |
406 ImGuiWindowFlags_NoFocusOnAppearing |
407 ImGuiWindowFlags_NoNav |
408 (reverseAllowMove ? 0 : ImGuiWindowFlags_NoMove)
409 : 0;
410 adjust.pre();
411 im::Window("Reverse bar", &showReverseBar, flags, [&]{
412 bool isOnMainViewPort = adjust.post();
413 auto b = reverseManager.getBegin();
414 auto e = reverseManager.getEnd();
415 auto c = reverseManager.getCurrent();
416
417 auto totalLength = e - b;
418 auto playLength = c - b;
419 auto recipLength = (totalLength != 0.0) ? (1.0 / totalLength) : 0.0;
420 auto fraction = narrow_cast<float>(playLength * recipLength);
421
422 gl::vec2 pos = ImGui::GetCursorScreenPos();
423 gl::vec2 availableSize = ImGui::GetContentRegionAvail();
424 gl::vec2 outerSize(availableSize.x, 2.0f * textHeight);
425 gl::vec2 outerTopLeft = pos;
426 gl::vec2 outerBottomRight = outerTopLeft + outerSize;
427
428 const auto& io = ImGui::GetIO();
429 bool hovered = ImGui::IsWindowHovered();
430 bool replaying = reverseManager.isReplaying();
431 if (!reverseHideTitle || !reverseFadeOut || replaying ||
432 ImGui::IsWindowDocked() || !isOnMainViewPort) {
433 reverseAlpha = 1.0f;
434 } else {
435 auto target = hovered ? 1.0f : 0.0f;
436 auto period = hovered ? 0.5f : 5.0f; // TODO configurable speed
437 reverseAlpha = calculateFade(reverseAlpha, target, period);
438 }
439 if (reverseAlpha != 0.0f) {
440 gl::vec2 innerSize = outerSize - gl::vec2(2, 2);
441 gl::vec2 innerTopLeft = outerTopLeft + gl::vec2(1, 1);
442 gl::vec2 innerBottomRight = innerTopLeft + innerSize;
443 gl::vec2 barBottomRight = innerTopLeft + gl::vec2(innerSize.x * fraction, innerSize.y);
444
445 gl::vec2 middleTopLeft (barBottomRight.x - 2.0f, innerTopLeft.y);
446 gl::vec2 middleBottomRight(barBottomRight.x + 2.0f, innerBottomRight.y);
447
448 auto color = [&](gl::vec4 col) {
449 return ImGui::ColorConvertFloat4ToU32(col * reverseAlpha);
450 };
451
452 auto* drawList = ImGui::GetWindowDrawList();
453 drawList->AddRectFilled(innerTopLeft, innerBottomRight, color(gl::vec4(0.0f, 0.0f, 0.0f, 0.5f)));
454
455 for (double s : reverseManager.getSnapshotTimes()) {
456 float x = narrow_cast<float>((s - b) * recipLength) * innerSize.x;
457 drawList->AddLine(gl::vec2(innerTopLeft.x + x, innerTopLeft.y),
458 gl::vec2(innerTopLeft.x + x, innerBottomRight.y),
459 color(gl::vec4(0.25f, 0.25f, 0.25f, 1.00f)));
460 }
461
462 static constexpr std::array barColors = {
463 std::array{gl::vec4(0.00f, 1.00f, 0.27f, 0.63f), gl::vec4(0.00f, 0.73f, 0.13f, 0.63f),
464 gl::vec4(0.07f, 0.80f, 0.80f, 0.63f), gl::vec4(0.00f, 0.87f, 0.20f, 0.63f)}, // view-only
465 std::array{gl::vec4(0.00f, 0.27f, 1.00f, 0.63f), gl::vec4(0.00f, 0.13f, 0.73f, 0.63f),
466 gl::vec4(0.07f, 0.80f, 0.80f, 0.63f), gl::vec4(0.00f, 0.20f, 0.87f, 0.63f)}, // replaying
467 std::array{gl::vec4(1.00f, 0.27f, 0.00f, 0.63f), gl::vec4(0.87f, 0.20f, 0.00f, 0.63f),
468 gl::vec4(0.80f, 0.80f, 0.07f, 0.63f), gl::vec4(0.73f, 0.13f, 0.00f, 0.63f)}, // recording
469 };
470 int barColorsIndex = replaying ? (reverseManager.isViewOnlyMode() ? 0 : 1)
471 : 2;
472 const auto& barColor = barColors[barColorsIndex];
473 drawList->AddRectFilledMultiColor(
474 innerTopLeft, barBottomRight,
475 color(barColor[0]), color(barColor[1]), color(barColor[2]), color(barColor[3]));
476
477 drawList->AddRectFilled(middleTopLeft, middleBottomRight, color(gl::vec4(1.0f, 0.5f, 0.0f, 0.75f)));
478 drawList->AddRect(
479 outerTopLeft, outerBottomRight, color(gl::vec4(1.0f)), 0.0f, 0, 2.0f);
480
481 auto timeStr = tmpStrCat(formatTime(playLength), " / ", formatTime(totalLength));
482 auto timeSize = ImGui::CalcTextSize(timeStr).x;
483 gl::vec2 cursor = ImGui::GetCursorPos();
484 ImGui::SetCursorPos(cursor + gl::vec2(std::max(0.0f, 0.5f * (outerSize.x - timeSize)), textHeight * 0.5f));
485 ImGui::TextColored(gl::vec4(1.0f) * reverseAlpha, "%s", timeStr.c_str());
486 ImGui::SetCursorPos(cursor); // restore position (for later ImGui::Dummy())
487 }
488
489 if (hovered && ImGui::IsMouseHoveringRect(outerTopLeft, outerBottomRight)) {
490 float ratio = (io.MousePos.x - pos.x) / outerSize.x;
491 auto timeOffset = totalLength * double(ratio);
492 im::Tooltip([&] {
494 });
495 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
496 manager.executeDelayed(makeTclList("reverse", "goto", b + timeOffset));
497 }
498 }
499
500 ImGui::Dummy(availableSize);
501 im::PopupContextItem("reverse context menu", [&]{
502 ImGui::Checkbox("Hide title", &reverseHideTitle);
503 im::Indent([&]{
504 im::Disabled(!reverseHideTitle, [&]{
505 ImGui::Checkbox("Fade out", &reverseFadeOut);
506 ImGui::Checkbox("Allow move", &reverseAllowMove);
507 });
508 });
509 });
510
511 if (reverseHideTitle && ImGui::IsWindowFocused()) {
512 ImGui::SetWindowFocus(nullptr); // give-up focus
513 }
514 });
515}
516
517} // namespace openmsx
bool loadLine(std::string_view name, zstring_view value)
void save(ImGuiTextBuffer &buf)
void printError(std::string_view message)
Definition CliComm.cc:17
std::optional< TclObject > execute(TclObject command)
void executeDelayed(std::function< void()> action)
ImGuiManager & manager
Definition ImGuiPart.hh:30
void loadLine(std::string_view name, zstring_view value) override
void save(ImGuiTextBuffer &buf) override
void showMenu(MSXMotherBoard *motherBoard) override
ReverseManager & getReverseManager()
static constexpr std::string_view REPLAY_DIR
static constexpr std::string_view REPLAY_EXTENSION
zstring_view getString() const
Definition TclObject.cc:141
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:39
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:26
vecN< 2, float > vec2
Definition gl_vec.hh:382
vecN< 4, float > vec4
Definition gl_vec.hh:384
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void PopupContextItem(const char *str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:421
void PopupModal(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:404
bool Menu(const char *label, bool enabled, std::invocable<> auto next)
Definition ImGuiCpp.hh:359
void Disabled(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:506
void Tooltip(std::invocable<> auto next)
Definition ImGuiCpp.hh:374
void Indent(float indent_w, std::invocable<> auto next)
Definition ImGuiCpp.hh:224
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
string parseCommandFileArgument(string_view argument, string_view directory, string_view prefix, string_view extension)
Helper function for parsing filename arguments in Tcl commands.
bool exists(zstring_view filename)
Does this file (directory) exists?
string getNextNumberedFileName(string_view directory, string_view prefix, string_view extension, bool addSeparator)
Gets the next numbered file name with the specified prefix in the specified directory,...
const string & getUserOpenMSXDir()
Get the openMSX dir in the user's home directory.
string_view stripExtension(string_view path)
Returns the path without extension.
string_view getFilename(string_view path)
Returns the file portion of a path name.
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void sortUpDown_String(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
void simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:79
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
std::string getShortCutForCommand(const HotKey &hotkey, std::string_view command)
void sortUpDown_T(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
bool foreach_file(std::string path, FileAction fileAction)
std::string formatTime(std::optional< double > time)
float calculateFade(float current, float target, float period)
TclObject makeTclList(Args &&... args)
Definition TclObject.hh:293
FileContext userDataFileContext(string_view subDir)
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
#define UNREACHABLE