@@ -60,6 +60,7 @@ App::App(std::string projectRoot, bool keepTempSessions, std::string tempRootOve
6060 : mProjectRoot(std::move(projectRoot))
6161 , mKeepTempSessions(keepTempSessions)
6262 , mTempRootOverride(std::move(tempRootOverride))
63+ , mDefaultLayoutMgr()
6364 , mSession(std::make_unique<EngineSession>()) {
6465 mLayoutPath = resolveProjectPath (kLayoutPaths [0 ]);
6566
@@ -70,6 +71,9 @@ App::App(std::string projectRoot, bool keepTempSessions, std::string tempRootOve
7071 appendEngineLog (" [GUI] Keeping temporary sessions for debugging is enabled." ,
7172 {1 .f , 0 .8f , 0 .2f , 1 .f });
7273 }
74+
75+ tryLoadDefaultLayoutOnStartup ();
76+
7377 appendEngineLog (" [GUI] Select a source and layout, then click START." );
7478
7579 int lw = 0 , lh = 0 , lch = 0 ;
@@ -305,6 +309,8 @@ void App::renderEngineTab() {
305309 if (!p.empty ()) { mLayoutPath = p; mLayoutPreset = IM_ARRAYSIZE (kLayoutNames ) - 1 ; }
306310 }
307311
312+ renderDefaultLayoutControls ();
313+
308314 ImGui::SetCursorPosX (120 .f );
309315 if (ImGui::Button (" Layout Builder##layoutbuilder" )) {
310316#ifdef __APPLE__
@@ -1105,6 +1111,116 @@ void App::doLaunchEngine(const std::string& scenePath,
11051111 appendEngineLog (" [Engine] Started successfully. OSC port 9009." , {0 .3f , 0 .9f , 0 .3f , 1 .f });
11061112}
11071113
1114+ void App::tryLoadDefaultLayoutOnStartup () {
1115+ const DefaultLayoutResult r = mDefaultLayoutMgr .loadDefaultLayout ();
1116+ mDefaultLayoutStatus = r.status ;
1117+ mDefaultLayoutSavedAt = r.savedAt ;
1118+ mDefaultLayoutName = r.layoutName ;
1119+ mDefaultLayoutSourcePath = r.sourcePath ;
1120+
1121+ if (r.status == DefaultLayoutStatus::None) {
1122+ appendEngineLog (" [GUI] No default layout saved." );
1123+ return ;
1124+ }
1125+ if (!r.success ) {
1126+ appendEngineLog (" [GUI] WARNING: Saved default layout could not be loaded: " + r.message ,
1127+ {1 .f , 0 .8f , 0 .2f , 1 .f });
1128+ return ;
1129+ }
1130+ // Write the validated JSON to a temp file so we can point mLayoutPath at it.
1131+ // We write it to the same settings dir — it IS the settings dir copy.
1132+ mLayoutPath = pathString (mDefaultLayoutMgr .layoutPath ());
1133+ mLayoutPreset = IM_ARRAYSIZE (kLayoutNames ) - 1 ; // "Custom"
1134+
1135+ const std::string displayName = r.layoutName .empty () ? " default layout" : r.layoutName ;
1136+ appendEngineLog (" [GUI] Default layout loaded: " + displayName, {0 .3f , 0 .9f , 0 .3f , 1 .f });
1137+ }
1138+
1139+ void App::onSetAsDefaultLayout () {
1140+ if (mLayoutPath .empty ()) {
1141+ appendEngineLog (" [GUI] No layout selected — cannot save as default." , {1 .f , 0 .5f , 0 .2f , 1 .f });
1142+ return ;
1143+ }
1144+ std::ifstream f (mLayoutPath );
1145+ if (!f.is_open ()) {
1146+ appendEngineLog (" [GUI] Cannot read layout file: " + mLayoutPath , {1 .f , 0 .4f , 0 .4f , 1 .f });
1147+ return ;
1148+ }
1149+ const std::string jsonText ((std::istreambuf_iterator<char >(f)),
1150+ std::istreambuf_iterator<char >());
1151+
1152+ const DefaultLayoutResult r = mDefaultLayoutMgr .saveDefaultLayout (jsonText, mLayoutPath );
1153+ mDefaultLayoutStatus = r.status ;
1154+ mDefaultLayoutSavedAt = r.savedAt ;
1155+ mDefaultLayoutName = r.layoutName ;
1156+ mDefaultLayoutSourcePath = r.sourcePath ;
1157+
1158+ if (r.success ) {
1159+ appendEngineLog (" [GUI] Default layout saved: " +
1160+ (r.layoutName .empty () ? mLayoutPath : r.layoutName ),
1161+ {0 .3f , 0 .9f , 0 .3f , 1 .f });
1162+ } else {
1163+ appendEngineLog (" [GUI] Failed to save default layout: " + r.message , {1 .f , 0 .4f , 0 .4f , 1 .f });
1164+ }
1165+ }
1166+
1167+ void App::onClearDefaultLayout () {
1168+ const DefaultLayoutResult r = mDefaultLayoutMgr .clearDefaultLayout ();
1169+ mDefaultLayoutStatus = DefaultLayoutStatus::None;
1170+ mDefaultLayoutSavedAt .clear ();
1171+ mDefaultLayoutName .clear ();
1172+ mDefaultLayoutSourcePath .clear ();
1173+ appendEngineLog (" [GUI] Default layout cleared." , {0 .65f , 0 .65f , 0 .9f , 1 .f });
1174+ (void )r;
1175+ }
1176+
1177+ void App::renderDefaultLayoutControls () {
1178+ const ImVec4 kGreen = {0 .20f , 0 .62f , 0 .25f , 1 .f };
1179+ const ImVec4 kAmber = {0 .70f , 0 .45f , 0 .08f , 1 .f };
1180+ const ImVec4 kRed = {0 .72f , 0 .18f , 0 .15f , 1 .f };
1181+ const ImVec4 kBlue = {0 .40f , 0 .60f , 0 .90f , 1 .f };
1182+
1183+ ImGui::TextDisabled (" DEFAULT LAYOUT" );
1184+ ImGui::SameLine (160 .f );
1185+
1186+ // Status indicator
1187+ switch (mDefaultLayoutStatus ) {
1188+ case DefaultLayoutStatus::None:
1189+ ImGui::TextDisabled (" none saved" );
1190+ break ;
1191+ case DefaultLayoutStatus::Loaded:
1192+ ImGui::TextColored (kGreen , " loaded" );
1193+ if (!mDefaultLayoutName .empty ()) {
1194+ ImGui::SameLine (); ImGui::TextDisabled (" (%s)" , mDefaultLayoutName .c_str ());
1195+ }
1196+ if (!mDefaultLayoutSavedAt .empty ()) {
1197+ ImGui::SameLine (); ImGui::TextDisabled (" saved %s" , mDefaultLayoutSavedAt .c_str ());
1198+ }
1199+ break ;
1200+ case DefaultLayoutStatus::Invalid:
1201+ ImGui::TextColored (kAmber , " saved file invalid — check layout JSON" );
1202+ break ;
1203+ case DefaultLayoutStatus::Unavailable:
1204+ ImGui::TextColored (kRed , " unavailable (permission/read error)" );
1205+ break ;
1206+ }
1207+
1208+ ImGui::SameLine (ImGui::GetContentRegionAvail ().x + ImGui::GetCursorPosX () - 300 .f );
1209+
1210+ const bool hasLayout = !mLayoutPath .empty ();
1211+ if (!hasLayout) ImGui::BeginDisabled (true );
1212+ if (ImGui::SmallButton (" Set as Default" )) onSetAsDefaultLayout ();
1213+ if (!hasLayout) ImGui::EndDisabled ();
1214+ if (ImGui::IsItemHovered ()) ImGui::SetTooltip (" Save current layout as startup default" );
1215+
1216+ ImGui::SameLine ();
1217+ const bool hasDefault = (mDefaultLayoutStatus != DefaultLayoutStatus::None);
1218+ if (!hasDefault) ImGui::BeginDisabled (true );
1219+ if (ImGui::SmallButton (" Clear Default" )) onClearDefaultLayout ();
1220+ if (!hasDefault) ImGui::EndDisabled ();
1221+ if (ImGui::IsItemHovered ()) ImGui::SetTooltip (" Remove the saved default layout" );
1222+ }
1223+
11081224void App::resetRuntimeToDefaults () {
11091225 mGainDb = 0 .0f ;
11101226 mFocus = 1 .5f ;
0 commit comments