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