Skip to content

Commit 525f8a8

Browse files
committed
refactor: extract open recent menu
1 parent 001c06f commit 525f8a8

4 files changed

Lines changed: 67 additions & 33 deletions

File tree

NEXT.md

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ selection-action groups, the Select-by-Type/Tag/Material, Align-Selection,
2525
Distribute-Selection, and Mirror-Selection submenus, and the Copy-Properties,
2626
Convert-to-Definition, Export-Subtree, Break-Instance, Drop-to-Ground,
2727
Snap-to-Grid, Group-Scale, Linear-Array, and Scatter-Along-Curve items plus the
28-
Group/Ungroup pair. The detailed toolbar Snap interval contents and the
29-
remaining MenuBar sections are still application-owned. Camera bookmark, walk,
28+
Group/Ungroup pair, plus every File-menu action through Open Recent. The detailed
29+
toolbar Snap interval contents and the remaining MenuBar sections are still
30+
application-owned. Camera bookmark, walk,
3031
document, undo, clipboard, selection, grid, and dialog state have **not** moved
3132
again: they remain in their existing editor/application owners; the UI
3233
component only reads presentation state and invokes application-owned
@@ -179,6 +180,17 @@ before when explicitly requested (`SYS-W14-##` rows).
179180
undo, clipboard, selection, grid, preferences, command-palette, and
180181
shortcut-dialog state remain in their existing owners; the menu receives
181182
only read-only state plus application-owned callbacks.
183+
- **Recently implemented (2026-07-25):** `SYS-W3-01` Phase 13 moved the
184+
`File → Open Recent` presentation into `Application::UI::MenuBar` through
185+
`FileOpenRecentContext`. It receives only current availability, a lazy
186+
recent-files provider, an open callback, and a clear callback. The component
187+
preserves the submenu label, numbered hidden IDs, basename labels, full-path
188+
tooltips, separator, and empty-list disabled state; persistence, unsaved
189+
document handling, loading, document replacement, and status/error reporting
190+
remain application-owned. `CCACHE_DISABLE=1 cmake --build b-release -j4
191+
--target MeshCraft` and `ctest --test-dir b-release -R '^mc3_commands$'
192+
--output-on-failure` passed; the latter already exercises recent-files'
193+
load, save, MRU de-duplication, cap, and restart round-trip contract.
182194
- **Recently implemented (2026-07-20 through 2026-07-25):** all 7
183195
raw-OpenGL(ES)-vs-CNA migrations, `AUD-082` through `AUD-088` — full
184196
detail with file:line evidence and
@@ -793,7 +805,7 @@ Edit-play-macro, Edit-macro-editor, Edit-lock-selection, Edit-reset-transform,
793805
Edit-transform-clipboard, Edit-isolate-selection, Edit-hide-selection,
794806
Edit-show-all-hidden, File-merge-scene, File-export-selection, File-export-GLB,
795807
File-export-OBJ, File-save, File-save-as, File-import-OBJ, File-new,
796-
File-open, and File-exit slices.
808+
File-open, File-open-recent, and File-exit slices.
797809
`EditHistoryContext` exposes only `canUndo`/`canRedo` plus Undo, Redo, and
798810
Open History callbacks; `Editor::UndoManager`, document replacement,
799811
selection restoration, dialog state, and keyboard handling remain
@@ -997,6 +1009,13 @@ pending-action state, dialog buffers and state, file loading, document
9971009
replacement, selection and undo initialization, title and status updates, error
9981010
handling, and the Ctrl+O keyboard route remain in their existing owners;
9991011
`MenuBar` owns only the unchanged label, shortcut, and click dispatch.
1012+
`FileOpenRecentContext` exposes only current availability, a lazy recent-files
1013+
provider, one open callback, and one clear callback. Recent-file storage and
1014+
persistence, unsaved-change handling, pending-action state, file loading,
1015+
document replacement, title/status updates, and error handling remain
1016+
application-owned; `MenuBar` owns the unchanged submenu label, numbered hidden
1017+
IDs, filename labels, full-path tooltips, separator, disabled state, and click
1018+
dispatch.
10001019
`FileExitContext` exposes only one exit callback. Unsaved-change handling,
10011020
pending-action state, application shutdown, all platform lifecycle work, and
10021021
the Escape fallback route remain in their existing owners; `MenuBar` owns only
@@ -1008,19 +1027,12 @@ The sequence below is planning only. Per `CLAUDE.md`, each item must be
10081027
described and explicitly confirmed immediately before implementation; finishing
10091028
one item does not authorize the next.
10101029

1011-
1. **File → Open Recent.** Extract the dynamic submenu with availability, a
1012-
lazy recent-files provider, one open-recent callback, and one clear callback.
1013-
`MenuBar` would preserve the submenu label, numbered hidden IDs, filename
1014-
labels, full-path tooltips, separator, and disabled behavior. Recent-file
1015-
storage and persistence, unsaved-change handling, pending-action state, file
1016-
loading, document replacement, title/status updates, and error handling stay
1017-
application-owned.
1018-
2. **View → Bloom/SSAO controls.** Audit and extract only the text-shader
1030+
1. **View → Bloom/SSAO controls.** Audit and extract only the text-shader
10191031
effects block, including its supported and unsupported presentation. Preserve
10201032
the conditional controls, labels, slider ranges, `AlwaysClamp` flags, and
10211033
existing state changes; renderer-capability checks and effect state remain
10221034
application-owned.
1023-
3. **Post-menu boundary audit.** Once the remaining File and View blocks are
1035+
2. **Post-menu boundary audit.** Once the remaining File and View blocks are
10241036
complete, identify one new narrow presentation boundary before changing
10251037
`MeshCraftApplication`. Do not start a broad application refactor.
10261038

include/MeshCraft/Application/UI/MenuBar.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "MeshCraft/Editor/CameraBookmarks.hpp"
44

5+
#include <filesystem>
56
#include <functional>
67
#include <set>
78
#include <string>
@@ -67,6 +68,13 @@ struct FileOpenContext {
6768
std::function<void()> requestOpenFile;
6869
};
6970

71+
struct FileOpenRecentContext {
72+
bool hasRecentFiles;
73+
std::function<const std::vector<std::filesystem::path>&()> getRecentFiles;
74+
std::function<void(const std::filesystem::path&)> requestOpenRecent;
75+
std::function<void()> clearRecent;
76+
};
77+
7078
struct FileExitContext {
7179
std::function<void()> requestExit;
7280
};
@@ -265,6 +273,7 @@ class MenuBar final {
265273
static void drawFileImportObj(const FileImportObjContext& context);
266274
static void drawFileNew(const FileNewContext& context);
267275
static void drawFileOpen(const FileOpenContext& context);
276+
static void drawFileOpenRecent(const FileOpenRecentContext& context);
268277
static void drawFileExit(const FileExitContext& context);
269278
static void drawEditClipboard(const EditClipboardContext& context);
270279
static void drawEditCopyProperties(const EditCopyPropertiesContext& context);

plan.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,13 @@ still internally consistent.
176176
Edit-reset-transform, Edit-transform-clipboard, Edit-isolate-selection,
177177
Edit-hide-selection, Edit-show-all-hidden, File-merge-scene,
178178
File-export-selection, File-export-GLB, File-export-OBJ, File-save,
179-
File-save-as, File-import-OBJ, File-new, File-open, and File-exit menu
180-
slices are implemented and verified. Their state remains in the existing
181-
editor/application owners;
179+
File-save-as, File-import-OBJ, File-new, File-open, File-open-recent, and
180+
File-exit menu slices are implemented and verified. Their state remains in
181+
the existing editor/application owners;
182182
`Application::UI::MenuBar` receives only the read-only values and callbacks
183183
required for presentation. The remaining ordered Phase 13 queue is:
184-
**File → Open Recent** (availability plus lazy provider/open/clear
185-
callbacks), then **View → Bloom/SSAO controls** (preserve the capability
186-
gate, ranges, and clamping), then a fresh audit for a new narrow boundary.
184+
**View → Bloom/SSAO controls** (preserve the capability gate, ranges, and
185+
clamping), then a fresh audit for a new narrow boundary.
187186
Every item requires its own confirmation per `CLAUDE.md`.
188187

189188
---
@@ -781,7 +780,8 @@ _All items in this workstream are DONE — archived to [`docs/history/plan_20260
781780
Edit-reset-transform, Edit-transform-clipboard, Edit-isolate-selection,
782781
Edit-hide-selection, Edit-show-all-hidden, File-merge-scene,
783782
File-export-selection, File-export-GLB, File-export-OBJ, File-save,
784-
File-save-as, File-import-OBJ, File-new, File-open, and File-exit slices,
783+
File-save-as, File-import-OBJ, File-new, File-open, File-open-recent, and
784+
File-exit slices,
785785
each incremental Release link and the same 147/147 + 34/34 partitions pass
786786
again.
787787
For the current MenuBar slices, the public UI header also compiles as a

src/MeshCraft/Application/UI/MenuBar.cpp

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,20 @@ float MeshCraftApplication::drawMenuBar()
5858
.requestOpenFile = [this] { confirmIfModified(PendingAction::OpenFile); },
5959
};
6060
UI::MenuBar::drawFileOpen(fileOpenContext);
61-
if (ImGui::BeginMenu("Open Recent", !recentFiles_.empty())) {
62-
for (int i = 0; i < static_cast<int>(recentFiles_.size()); ++i) {
63-
const auto& rf = recentFiles_[static_cast<size_t>(i)];
64-
std::string label = rf.filename().string() + "##rf" + std::to_string(i);
65-
if (ImGui::MenuItem(label.c_str()))
66-
confirmIfModified(PendingAction::OpenRecentFile, rf);
67-
if (ImGui::IsItemHovered())
68-
ImGui::SetTooltip("%s", rf.string().c_str());
69-
}
70-
ImGui::Separator();
71-
if (ImGui::MenuItem("Clear Recent")) {
61+
const UI::FileOpenRecentContext fileOpenRecentContext{
62+
.hasRecentFiles = !recentFiles_.empty(),
63+
.getRecentFiles = [this]() -> const std::vector<std::filesystem::path>& {
64+
return recentFiles_;
65+
},
66+
.requestOpenRecent = [this](const std::filesystem::path& path) {
67+
confirmIfModified(PendingAction::OpenRecentFile, path);
68+
},
69+
.clearRecent = [this] {
7270
recentFiles_.clear();
7371
saveRecentFiles();
74-
}
75-
ImGui::EndMenu();
76-
}
72+
},
73+
};
74+
UI::MenuBar::drawFileOpenRecent(fileOpenRecentContext);
7775
// STAB-0717: previously OBJ was only reachable indirectly, as a
7876
// per-object mesh-source path typed into an existing Mesh
7977
// object's Properties panel field (PropertiesPanel.cpp's
@@ -645,6 +643,21 @@ void MenuBar::drawFileOpen(const FileOpenContext& context) {
645643
if (ImGui::MenuItem("Open...", "Ctrl+O")) context.requestOpenFile();
646644
}
647645

646+
void MenuBar::drawFileOpenRecent(const FileOpenRecentContext& context) {
647+
if (!ImGui::BeginMenu("Open Recent", context.hasRecentFiles)) return;
648+
649+
const auto& recentFiles = context.getRecentFiles();
650+
for (int i = 0; i < static_cast<int>(recentFiles.size()); ++i) {
651+
const auto& path = recentFiles[static_cast<size_t>(i)];
652+
std::string label = path.filename().string() + "##rf" + std::to_string(i);
653+
if (ImGui::MenuItem(label.c_str())) context.requestOpenRecent(path);
654+
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", path.string().c_str());
655+
}
656+
ImGui::Separator();
657+
if (ImGui::MenuItem("Clear Recent")) context.clearRecent();
658+
ImGui::EndMenu();
659+
}
660+
648661
void MenuBar::drawFileExit(const FileExitContext& context) {
649662
if (ImGui::MenuItem("Exit")) context.requestExit();
650663
}

0 commit comments

Comments
 (0)