yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
session_coordinator.cc
Go to the documentation of this file.
2#include <absl/status/status.h>
3#include <absl/status/statusor.h>
4
5#include <algorithm>
6#include <cstdint>
7#include <cstdio>
8#include <cstring>
9#include <filesystem>
10#include <memory>
11#include <stdexcept>
12#include <string>
13#include <utility>
14
15#include "absl/strings/str_format.h"
23#include "app/gui/core/icons.h"
28#include "core/color.h"
29#include "editor/editor.h"
31#include "imgui/imgui.h"
32#include "util/log.h"
33#include "zelda3/game_data.h"
34
35namespace yaze {
36namespace editor {
37
38SessionCoordinator::SessionCoordinator(WorkspaceWindowManager* window_manager,
39 ToastManager* toast_manager,
40 UserSettings* user_settings)
41 : window_manager_(window_manager),
42 toast_manager_(toast_manager),
43 user_settings_(user_settings) {}
44
45void SessionCoordinator::NotifySessionSwitched(size_t index,
46 RomSession* session) {
47 // Publish event to EventBus
48 if (event_bus_) {
49 // Track previous index for the event (before we update active_session_index_)
50 size_t old_index = active_session_index_;
51 event_bus_->Publish(
52 SessionSwitchedEvent::Create(old_index, index, session));
53 }
54}
55
56void SessionCoordinator::NotifySessionCreated(size_t index,
57 RomSession* session) {
58 // Publish event to EventBus
59 if (event_bus_) {
60 event_bus_->Publish(SessionCreatedEvent::Create(index, session));
61 }
62}
63
64void SessionCoordinator::NotifySessionClosed(size_t index) {
65 // Publish event to EventBus
66 if (event_bus_) {
67 event_bus_->Publish(SessionClosedEvent::Create(index));
68 }
69}
70
71void SessionCoordinator::NotifySessionRomLoaded(size_t index,
72 RomSession* session) {
73 // Publish event to EventBus
74 if (event_bus_ && session) {
75 event_bus_->Publish(
76 RomLoadedEvent::Create(&session->rom, session->filepath, index));
77 }
78}
79
80void SessionCoordinator::CreateNewSession() {
81 if (session_count_ >= kMaxSessions) {
82 ShowSessionLimitWarning();
83 return;
84 }
85
86 // Create new empty session
87 sessions_.push_back(std::make_unique<RomSession>());
88 UpdateSessionCount();
89
90 // Set as active session
91 active_session_index_ = sessions_.size() - 1;
92
93 // Configure the new session
94 if (editor_manager_) {
95 auto& session = sessions_.back();
96 editor_manager_->ConfigureSession(session.get());
97 }
98
99 LOG_INFO("SessionCoordinator", "Created new session %zu (total: %zu)",
100 active_session_index_, session_count_);
101
102 // Notify observers
103 NotifySessionCreated(active_session_index_, sessions_.back().get());
104
105 ShowSessionOperationResult("Create Session", true);
106}
107
108void SessionCoordinator::DuplicateCurrentSession() {
109 if (sessions_.empty())
110 return;
111
112 if (session_count_ >= kMaxSessions) {
113 ShowSessionLimitWarning();
114 return;
115 }
116
117 // Create new empty session (cannot actually duplicate due to non-movable
118 // editors)
119 // TODO: Implement proper duplication when editors become movable
120 sessions_.push_back(std::make_unique<RomSession>());
121 UpdateSessionCount();
122
123 // Set as active session
124 active_session_index_ = sessions_.size() - 1;
125
126 // Configure the new session
127 if (editor_manager_) {
128 auto& session = sessions_.back();
129 editor_manager_->ConfigureSession(session.get());
130 }
131
132 LOG_INFO("SessionCoordinator", "Duplicated session %zu (total: %zu)",
133 active_session_index_, session_count_);
134
135 // Notify observers
136 NotifySessionCreated(active_session_index_, sessions_.back().get());
137
138 ShowSessionOperationResult("Duplicate Session", true);
139}
140
141void SessionCoordinator::CloseCurrentSession() {
142 CloseSession(active_session_index_);
143}
144
145void SessionCoordinator::CloseSession(size_t index) {
146 if (!IsValidSessionIndex(index))
147 return;
148
149 if (session_count_ <= kMinSessions) {
150 // Don't allow closing the last session
151 if (toast_manager_) {
152 toast_manager_->Show("Cannot close the last session",
153 ToastType::kWarning);
154 }
155 return;
156 }
157
158 // Unregister cards for this session
159 if (window_manager_) {
160 window_manager_->UnregisterSession(index);
161 }
162
163 // Notify observers before removal
164 NotifySessionClosed(index);
165
166 // Remove session (safe now with unique_ptr!)
167 sessions_.erase(sessions_.begin() + index);
168 UpdateSessionCount();
169
170 // Adjust active session index
171 if (active_session_index_ >= index && active_session_index_ > 0) {
172 active_session_index_--;
173 }
174
175 LOG_INFO("SessionCoordinator", "Closed session %zu (total: %zu)", index,
176 session_count_);
177
178 ShowSessionOperationResult("Close Session", true);
179}
180
181void SessionCoordinator::RemoveSession(size_t index) {
182 CloseSession(index);
183}
184
185void SessionCoordinator::SwitchToSession(size_t index) {
186 if (!IsValidSessionIndex(index))
187 return;
188
189 size_t old_index = active_session_index_;
190 active_session_index_ = index;
191
192 if (window_manager_) {
193 window_manager_->SetActiveSession(index);
194 }
195
196 // Only notify if actually switching to a different session
197 if (old_index != index) {
198 NotifySessionSwitched(index, sessions_[index].get());
199 }
200}
201
202void SessionCoordinator::ActivateSession(size_t index) {
203 SwitchToSession(index);
204}
205
206size_t SessionCoordinator::GetActiveSessionIndex() const {
207 return active_session_index_;
208}
209
210void* SessionCoordinator::GetActiveSession() const {
211 if (!IsValidSessionIndex(active_session_index_)) {
212 return nullptr;
213 }
214 return sessions_[active_session_index_].get();
215}
216
217RomSession* SessionCoordinator::GetActiveRomSession() const {
218 return static_cast<RomSession*>(GetActiveSession());
219}
220
221Rom* SessionCoordinator::GetCurrentRom() const {
222 auto* session = GetActiveRomSession();
223 return session ? &session->rom : nullptr;
224}
225
226zelda3::GameData* SessionCoordinator::GetCurrentGameData() const {
227 auto* session = GetActiveRomSession();
228 return session ? &session->game_data : nullptr;
229}
230
231EditorSet* SessionCoordinator::GetCurrentEditorSet() const {
232 auto* session = GetActiveRomSession();
233 return session ? &session->editors : nullptr;
234}
235
236void* SessionCoordinator::GetSession(size_t index) const {
237 if (!IsValidSessionIndex(index)) {
238 return nullptr;
239 }
240 return sessions_[index].get();
241}
242
243bool SessionCoordinator::HasMultipleSessions() const {
244 return session_count_ > 1;
245}
246
247size_t SessionCoordinator::GetActiveSessionCount() const {
248 return session_count_;
249}
250
251bool SessionCoordinator::HasDuplicateSession(
252 const std::string& filepath) const {
253 if (filepath.empty())
254 return false;
255
256 for (const auto& session : sessions_) {
257 if (session->filepath == filepath) {
258 return true;
259 }
260 }
261 return false;
262}
263
264void SessionCoordinator::DrawSessionSwitcher() {
265 if (sessions_.empty())
266 return;
267
268 if (!show_session_switcher_)
269 return;
270
271 ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver);
272 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
273 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
274
275 if (!ImGui::Begin("Session Switcher", &show_session_switcher_)) {
276 ImGui::End();
277 return;
278 }
279
280 ImGui::Text("%s Active Sessions (%zu)", ICON_MD_TAB, session_count_);
281 ImGui::Separator();
282
283 for (size_t i = 0; i < sessions_.size(); ++i) {
284 bool is_active = (i == active_session_index_);
285
286 ImGui::PushID(static_cast<int>(i));
287
288 // Session tab
289 if (ImGui::Selectable(GetSessionDisplayName(i).c_str(), is_active)) {
290 SwitchToSession(i);
291 }
292
293 // Right-click context menu
294 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
295 ImGui::OpenPopup("SessionContextMenu");
296 }
297
298 if (ImGui::BeginPopup("SessionContextMenu")) {
299 DrawSessionContextMenu(i);
300 ImGui::EndPopup();
301 }
302
303 ImGui::PopID();
304 }
305
306 ImGui::Separator();
307
308 // Action buttons
309 if (ImGui::Button(absl::StrFormat("%s New Session", ICON_MD_ADD).c_str())) {
310 CreateNewSession();
311 }
312
313 ImGui::SameLine();
314 if (ImGui::Button(
315 absl::StrFormat("%s Duplicate", ICON_MD_CONTENT_COPY).c_str())) {
316 DuplicateCurrentSession();
317 }
318
319 ImGui::SameLine();
320 if (HasMultipleSessions() &&
321 ImGui::Button(absl::StrFormat("%s Close", ICON_MD_CLOSE).c_str())) {
322 CloseCurrentSession();
323 }
324
325 ImGui::End();
326}
327
328void SessionCoordinator::DrawSessionManager() {
329 if (sessions_.empty())
330 return;
331
332 if (!show_session_manager_)
333 return;
334
335 ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver);
336 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
337 ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
338
339 if (!ImGui::Begin("Session Manager", &show_session_manager_)) {
340 ImGui::End();
341 return;
342 }
343
344 // Session statistics
345 ImGui::Text("%s Session Statistics", ICON_MD_ANALYTICS);
346 ImGui::Separator();
347
348 ImGui::Text("Total Sessions: %zu", GetTotalSessionCount());
349 ImGui::Text("Loaded Sessions: %zu", GetLoadedSessionCount());
350 ImGui::Text("Empty Sessions: %zu", GetEmptySessionCount());
351
352 ImGui::Spacing();
353
354 // Session list
355 if (ImGui::BeginTable("SessionTable", 4,
356 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
357 ImGuiTableFlags_Resizable)) {
358 ImGui::TableSetupColumn("Session", ImGuiTableColumnFlags_WidthStretch,
359 0.3f);
360 ImGui::TableSetupColumn("ROM File", ImGuiTableColumnFlags_WidthStretch,
361 0.4f);
362 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthStretch, 0.2f);
363 ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed,
364 120.0f);
365 ImGui::TableHeadersRow();
366
367 for (size_t i = 0; i < sessions_.size(); ++i) {
368 const auto& session = sessions_[i];
369 bool is_active = (i == active_session_index_);
370
371 ImGui::PushID(static_cast<int>(i));
372
373 ImGui::TableNextRow();
374
375 // Session name
376 ImGui::TableNextColumn();
377 if (is_active) {
378 ImGui::TextColored(gui::GetSuccessColor(), "%s %s",
380 GetSessionDisplayName(i).c_str());
381 } else {
382 ImGui::Text("%s %s", ICON_MD_RADIO_BUTTON_UNCHECKED,
383 GetSessionDisplayName(i).c_str());
384 }
385
386 // ROM file
387 ImGui::TableNextColumn();
388 if (session->rom.is_loaded()) {
389 ImGui::Text("%s", session->filepath.c_str());
390 } else {
391 ImGui::TextDisabled("(No ROM loaded)");
392 }
393
394 // Status
395 ImGui::TableNextColumn();
396 if (session->rom.is_loaded()) {
397 ImGui::TextColored(gui::GetSuccessColor(), "Loaded");
398 } else {
399 ImGui::TextColored(gui::GetWarningColor(), "Empty");
400 }
401
402 // Actions
403 ImGui::TableNextColumn();
404 if (!is_active && ImGui::SmallButton("Switch")) {
405 SwitchToSession(i);
406 }
407
408 ImGui::SameLine();
409 if (HasMultipleSessions() && ImGui::SmallButton("Close")) {
410 CloseSession(i);
411 }
412
413 ImGui::PopID();
414 }
415
416 ImGui::EndTable();
417 }
418
419 ImGui::End();
420}
421
422void SessionCoordinator::DrawSessionRenameDialog() {
423 if (!show_session_rename_dialog_)
424 return;
425
426 ImGui::SetNextWindowSize(ImVec2(300, 150), ImGuiCond_Always);
427 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
428 ImGuiCond_Always, ImVec2(0.5f, 0.5f));
429
430 if (!ImGui::Begin("Rename Session", &show_session_rename_dialog_)) {
431 ImGui::End();
432 return;
433 }
434
435 ImGui::Text("Rename session %zu:", session_to_rename_);
436 ImGui::InputText("Name", session_rename_buffer_,
437 sizeof(session_rename_buffer_));
438
439 ImGui::Spacing();
440
441 if (ImGui::Button("OK")) {
442 RenameSession(session_to_rename_, session_rename_buffer_);
443 show_session_rename_dialog_ = false;
444 session_rename_buffer_[0] = '\0';
445 }
446
447 ImGui::SameLine();
448 if (ImGui::Button("Cancel")) {
449 show_session_rename_dialog_ = false;
450 session_rename_buffer_[0] = '\0';
451 }
452
453 ImGui::End();
454}
455
456void SessionCoordinator::DrawSessionTabs() {
457 if (sessions_.empty())
458 return;
459
460 if (gui::BeginThemedTabBar("SessionTabs")) {
461 for (size_t i = 0; i < sessions_.size(); ++i) {
462 bool is_active = (i == active_session_index_);
463 const auto& session = sessions_[i];
464
465 std::string tab_name = GetSessionDisplayName(i);
466 if (session->rom.is_loaded()) {
467 tab_name += " ";
468 tab_name += ICON_MD_CHECK_CIRCLE;
469 }
470
471 if (ImGui::BeginTabItem(tab_name.c_str())) {
472 if (!is_active) {
473 SwitchToSession(i);
474 }
475 ImGui::EndTabItem();
476 }
477
478 // Right-click context menu
479 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
480 ImGui::OpenPopup(absl::StrFormat("SessionTabContext_%zu", i).c_str());
481 }
482
483 if (ImGui::BeginPopup(
484 absl::StrFormat("SessionTabContext_%zu", i).c_str())) {
485 DrawSessionContextMenu(i);
486 ImGui::EndPopup();
487 }
488 }
489 gui::EndThemedTabBar();
490 }
491}
492
493void SessionCoordinator::DrawSessionIndicator() {
494 if (!HasMultipleSessions())
495 return;
496
497 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
498 ImVec4 accent_color = ConvertColorToImVec4(theme.accent);
499
500 {
501 gui::StyleColorGuard accent_guard(ImGuiCol_Text, accent_color);
502 ImGui::Text("%s Session %zu", ICON_MD_TAB, active_session_index_);
503 }
504
505 if (ImGui::IsItemHovered()) {
506 ImGui::SetTooltip("Active Session: %s\nClick to open session switcher",
507 GetActiveSessionDisplayName().c_str());
508 }
509
510 if (ImGui::IsItemClicked()) {
511 ToggleSessionSwitcher();
512 }
513}
514
515std::string SessionCoordinator::GetSessionDisplayName(size_t index) const {
516 if (!IsValidSessionIndex(index)) {
517 return "Invalid Session";
518 }
519
520 const auto& session = sessions_[index];
521
522 if (!session->custom_name.empty()) {
523 return session->custom_name;
524 }
525
526 if (session->rom.is_loaded()) {
527 return absl::StrFormat(
528 "Session %zu (%s)", index,
529 std::filesystem::path(session->filepath).stem().string());
530 }
531
532 return absl::StrFormat("Session %zu (Empty)", index);
533}
534
535std::string SessionCoordinator::GetActiveSessionDisplayName() const {
536 return GetSessionDisplayName(active_session_index_);
537}
538
539void SessionCoordinator::RenameSession(size_t index,
540 const std::string& new_name) {
541 if (!IsValidSessionIndex(index) || new_name.empty())
542 return;
543
544 sessions_[index]->custom_name = new_name;
545 LOG_INFO("SessionCoordinator", "Renamed session %zu to '%s'", index,
546 new_name.c_str());
547}
548
549std::string SessionCoordinator::GenerateUniqueEditorTitle(
550 const std::string& editor_name, size_t session_index) const {
551 if (sessions_.size() <= 1) {
552 // Single session - use simple name
553 return editor_name;
554 }
555
556 if (session_index >= sessions_.size()) {
557 return editor_name;
558 }
559
560 // Multi-session - include session identifier
561 const auto& session = sessions_[session_index];
562 std::string session_name = session->custom_name.empty()
563 ? session->rom.title()
564 : session->custom_name;
565
566 // Truncate long session names
567 if (session_name.length() > 20) {
568 session_name = session_name.substr(0, 17) + "...";
569 }
570
571 return absl::StrFormat("%s - %s##session_%zu", editor_name, session_name,
572 session_index);
573}
574
575void SessionCoordinator::SetActiveSessionIndex(size_t index) {
576 SwitchToSession(index);
577}
578
579void SessionCoordinator::UpdateSessionCount() {
580 session_count_ = sessions_.size();
581}
582
583// Panel coordination across sessions
584void SessionCoordinator::ShowAllPanelsInActiveSession() {
585 if (window_manager_) {
586 window_manager_->ShowAllWindowsInSession(active_session_index_);
587 }
588}
589
590void SessionCoordinator::HideAllPanelsInActiveSession() {
591 if (window_manager_) {
592 window_manager_->HideAllWindowsInSession(active_session_index_);
593 }
594}
595
596void SessionCoordinator::ShowPanelsInCategory(const std::string& category) {
597 if (window_manager_) {
598 window_manager_->ShowAllWindowsInCategory(active_session_index_, category);
599 }
600}
601
602void SessionCoordinator::HidePanelsInCategory(const std::string& category) {
603 if (window_manager_) {
604 window_manager_->HideAllWindowsInCategory(active_session_index_, category);
605 }
606}
607
608bool SessionCoordinator::IsValidSessionIndex(size_t index) const {
609 return index < sessions_.size();
610}
611
612void SessionCoordinator::UpdateSessions() {
613 if (sessions_.empty())
614 return;
615
616 size_t original_session_idx = active_session_index_;
617
618 for (size_t session_idx = 0; session_idx < sessions_.size(); ++session_idx) {
619 auto& session = sessions_[session_idx];
620 const bool rom_loaded = session->rom.is_loaded();
621 // Skip empty sessions except the active one so pre-ROM tooling (e.g.
622 // Graphics prototype research, Assembly folder editing) can still tick.
623 if (!rom_loaded && session_idx != active_session_index_) {
624 continue;
625 }
626
627 // Switch context
628 SwitchToSession(session_idx);
629
630 for (auto editor : session->editors.active_editors_) {
631 if (*editor->active()) {
632 if (!rom_loaded &&
633 !EditorRegistry::UpdateAllowedWithoutLoadedRom(editor->type())) {
634 continue;
635 }
636
637 if (rom_loaded && editor->type() == EditorType::kOverworld) {
638 auto& overworld_editor = static_cast<OverworldEditor&>(*editor);
639 if (overworld_editor.jump_to_tab() != -1) {
640 // Set the dungeon editor to the jump to tab
641 session->editors.GetDungeonEditor()->add_room(
642 overworld_editor.jump_to_tab());
643 overworld_editor.jump_to_tab_ = -1;
644 }
645 }
646
647 // CARD-BASED EDITORS: Don't wrap in Begin/End, they manage own windows
648 bool is_card_based_editor =
649 EditorManager::IsPanelBasedEditor(editor->type());
650
651 if (is_card_based_editor) {
652 // Panel-based editors create their own top-level windows
653 // No parent wrapper needed - this allows independent docking
654 if (editor_manager_) {
655 editor_manager_->SetCurrentEditor(editor);
656 }
657
658 absl::Status status = editor->Update();
659
660 // Route editor errors to toast manager
661 if (!status.ok() && toast_manager_) {
662 std::string editor_name =
663 kEditorNames[static_cast<int>(editor->type())];
664 toast_manager_->Show(
665 absl::StrFormat("%s Error: %s", editor_name, status.message()),
666 ToastType::kError, 8.0f);
667 }
668
669 } else {
670 // TRADITIONAL EDITORS: Wrap in Begin/End
671 std::string window_title = GenerateUniqueEditorTitle(
672 kEditorNames[static_cast<int>(editor->type())], session_idx);
673
674 // Set window to maximize on first open
675 ImGui::SetNextWindowSize(ImGui::GetMainViewport()->WorkSize,
676 ImGuiCond_FirstUseEver);
677 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->WorkPos,
678 ImGuiCond_FirstUseEver);
679
680 if (ImGui::Begin(window_title.c_str(), editor->active(),
681 ImGuiWindowFlags_None)) { // Allow full docking
682 // Temporarily switch context for this editor's update
683 // (Already switched via SwitchToSession)
684 if (editor_manager_) {
685 editor_manager_->SetCurrentEditor(editor);
686 }
687
688 absl::Status status = editor->Update();
689
690 // Route editor errors to toast manager
691 if (!status.ok() && toast_manager_) {
692 std::string editor_name =
693 kEditorNames[static_cast<int>(editor->type())];
694 toast_manager_->Show(absl::StrFormat("%s Error: %s", editor_name,
695 status.message()),
696 ToastType::kError, 8.0f);
697 }
698 }
699 ImGui::End();
700 }
701 }
702 }
703 }
704
705 // Restore original session context
706 SwitchToSession(original_session_idx);
707}
708
709bool SessionCoordinator::IsSessionActive(size_t index) const {
710 return index == active_session_index_;
711}
712
713bool SessionCoordinator::IsSessionLoaded(size_t index) const {
714 return IsValidSessionIndex(index) && sessions_[index]->rom.is_loaded();
715}
716
717size_t SessionCoordinator::GetTotalSessionCount() const {
718 return session_count_;
719}
720
721size_t SessionCoordinator::GetLoadedSessionCount() const {
722 size_t count = 0;
723 for (const auto& session : sessions_) {
724 if (session->rom.is_loaded()) {
725 count++;
726 }
727 }
728 return count;
729}
730
731size_t SessionCoordinator::GetEmptySessionCount() const {
732 return session_count_ - GetLoadedSessionCount();
733}
734
735absl::Status SessionCoordinator::LoadRomIntoSession(const std::string& filename,
736 size_t session_index) {
737 if (filename.empty()) {
738 return absl::InvalidArgumentError("Invalid parameters");
739 }
740
741 size_t target_index =
742 (session_index == SIZE_MAX) ? active_session_index_ : session_index;
743 if (!IsValidSessionIndex(target_index)) {
744 return absl::InvalidArgumentError("Invalid session index");
745 }
746
747 // TODO: Implement actual ROM loading
748 LOG_INFO("SessionCoordinator", "LoadRomIntoSession: %s -> session %zu",
749 filename.c_str(), target_index);
750
751 return absl::OkStatus();
752}
753
754absl::Status SessionCoordinator::SaveActiveSession(
755 const std::string& filename) {
756 if (!IsValidSessionIndex(active_session_index_)) {
757 return absl::FailedPreconditionError("No active session");
758 }
759
760 // TODO: Implement actual ROM saving
761 LOG_INFO("SessionCoordinator", "SaveActiveSession: session %zu",
762 active_session_index_);
763
764 return absl::OkStatus();
765}
766
767absl::Status SessionCoordinator::SaveSessionAs(size_t session_index,
768 const std::string& filename) {
769 if (!IsValidSessionIndex(session_index) || filename.empty()) {
770 return absl::InvalidArgumentError("Invalid parameters");
771 }
772
773 // TODO: Implement actual ROM saving
774 LOG_INFO("SessionCoordinator", "SaveSessionAs: session %zu -> %s",
775 session_index, filename.c_str());
776
777 return absl::OkStatus();
778}
779
780absl::StatusOr<RomSession*> SessionCoordinator::CreateSessionFromRom(
781 Rom&& rom, const std::string& filepath) {
782 size_t new_session_id = sessions_.size();
783 sessions_.push_back(std::make_unique<RomSession>(
784 std::move(rom), user_settings_, new_session_id, editor_registry_));
785 auto& session = sessions_.back();
786 session->filepath = filepath;
787
788 UpdateSessionCount();
789 SwitchToSession(new_session_id);
790
791 // Notify observers
792 NotifySessionCreated(new_session_id, session.get());
793 NotifySessionRomLoaded(new_session_id, session.get());
794
795 return session.get();
796}
797
798void SessionCoordinator::CleanupClosedSessions() {
799 // Mark empty sessions as closed (except keep at least one)
800 size_t loaded_count = 0;
801 for (const auto& session : sessions_) {
802 if (session->rom.is_loaded()) {
803 loaded_count++;
804 }
805 }
806
807 if (loaded_count > 0) {
808 for (auto it = sessions_.begin(); it != sessions_.end();) {
809 if (!(*it)->rom.is_loaded() && sessions_.size() > 1) {
810 it = sessions_.erase(it);
811 } else {
812 ++it;
813 }
814 }
815 }
816
817 UpdateSessionCount();
818 LOG_INFO("SessionCoordinator", "Cleaned up closed sessions (remaining: %zu)",
819 session_count_);
820}
821
822void SessionCoordinator::ClearAllSessions() {
823 if (sessions_.empty())
824 return;
825
826 // Unregister all session cards
827 if (window_manager_) {
828 for (size_t i = 0; i < sessions_.size(); ++i) {
829 window_manager_->UnregisterSession(i);
830 }
831 }
832
833 sessions_.clear();
834 active_session_index_ = 0;
835 UpdateSessionCount();
836
837 LOG_INFO("SessionCoordinator", "Cleared all sessions");
838}
839
840void SessionCoordinator::FocusNextSession() {
841 if (sessions_.empty())
842 return;
843
844 size_t next_index = (active_session_index_ + 1) % sessions_.size();
845 SwitchToSession(next_index);
846}
847
848void SessionCoordinator::FocusPreviousSession() {
849 if (sessions_.empty())
850 return;
851
852 size_t prev_index = (active_session_index_ == 0) ? sessions_.size() - 1
853 : active_session_index_ - 1;
854 SwitchToSession(prev_index);
855}
856
857void SessionCoordinator::FocusFirstSession() {
858 if (sessions_.empty())
859 return;
860 SwitchToSession(0);
861}
862
863void SessionCoordinator::FocusLastSession() {
864 if (sessions_.empty())
865 return;
866 SwitchToSession(sessions_.size() - 1);
867}
868
869void SessionCoordinator::UpdateActiveSession() {
870 if (!sessions_.empty() && active_session_index_ >= sessions_.size()) {
871 active_session_index_ = sessions_.size() - 1;
872 }
873}
874
875void SessionCoordinator::ValidateSessionIndex(size_t index) const {
876 if (!IsValidSessionIndex(index)) {
877 throw std::out_of_range(
878 absl::StrFormat("Invalid session index: %zu", index));
879 }
880}
881
882std::string SessionCoordinator::GenerateUniqueSessionName(
883 const std::string& base_name) const {
884 if (sessions_.empty())
885 return base_name;
886
887 std::string name = base_name;
888 int counter = 1;
889
890 while (true) {
891 bool found = false;
892 for (const auto& session : sessions_) {
893 if (session->custom_name == name) {
894 found = true;
895 break;
896 }
897 }
898
899 if (!found)
900 break;
901
902 name = absl::StrFormat("%s %d", base_name, counter++);
903 }
904
905 return name;
906}
907
908void SessionCoordinator::ShowSessionLimitWarning() {
909 if (toast_manager_) {
910 toast_manager_->Show(
911 absl::StrFormat("Maximum %zu sessions allowed", kMaxSessions),
912 ToastType::kWarning);
913 }
914}
915
916void SessionCoordinator::ShowSessionOperationResult(
917 const std::string& operation, bool success) {
918 if (toast_manager_) {
919 std::string message =
920 absl::StrFormat("%s %s", operation, success ? "succeeded" : "failed");
921 ToastType type = success ? ToastType::kSuccess : ToastType::kError;
922 toast_manager_->Show(message, type);
923 }
924}
925
926void SessionCoordinator::DrawSessionTab(size_t index, bool is_active) {
927 if (index >= sessions_.size())
928 return;
929
930 const auto& session = sessions_[index];
931
932 ImVec4 color = GetSessionColor(index);
933 gui::StyleColorGuard tab_color_guard(ImGuiCol_Text, color);
934
935 std::string tab_name = GetSessionDisplayName(index);
936 if (session->rom.is_loaded()) {
937 tab_name += " ";
938 tab_name += ICON_MD_CHECK_CIRCLE;
939 }
940
941 if (ImGui::BeginTabItem(tab_name.c_str())) {
942 if (!is_active) {
943 SwitchToSession(index);
944 }
945 ImGui::EndTabItem();
946 }
947}
948
949void SessionCoordinator::DrawSessionContextMenu(size_t index) {
950 if (ImGui::MenuItem(
951 absl::StrFormat("%s Switch to Session", ICON_MD_TAB).c_str())) {
952 SwitchToSession(index);
953 }
954
955 if (ImGui::MenuItem(absl::StrFormat("%s Rename", ICON_MD_EDIT).c_str())) {
956 session_to_rename_ = index;
957 strncpy(session_rename_buffer_, GetSessionDisplayName(index).c_str(),
958 sizeof(session_rename_buffer_) - 1);
959 session_rename_buffer_[sizeof(session_rename_buffer_) - 1] = '\0';
960 show_session_rename_dialog_ = true;
961 }
962
963 if (ImGui::MenuItem(
964 absl::StrFormat("%s Duplicate", ICON_MD_CONTENT_COPY).c_str())) {
965 // TODO: Implement session duplication
966 }
967
968 ImGui::Separator();
969
970 if (HasMultipleSessions() &&
971 ImGui::MenuItem(
972 absl::StrFormat("%s Close Session", ICON_MD_CLOSE).c_str())) {
973 CloseSession(index);
974 }
975}
976
977void SessionCoordinator::DrawSessionBadge(size_t index) {
978 if (index >= sessions_.size())
979 return;
980
981 const auto& session = sessions_[index];
982 ImVec4 color = GetSessionColor(index);
983
984 gui::StyleColorGuard badge_guard(ImGuiCol_Text, color);
985
986 if (session->rom.is_loaded()) {
987 ImGui::Text("%s", ICON_MD_CHECK_CIRCLE);
988 } else {
989 ImGui::Text("%s", ICON_MD_RADIO_BUTTON_UNCHECKED);
990 }
991}
992
993ImVec4 SessionCoordinator::GetSessionColor(size_t index) const {
994 // Generate consistent colors for sessions
995 static const ImVec4 colors[] = {
996 ImVec4(0.0f, 1.0f, 0.0f, 1.0f), // Green
997 ImVec4(0.0f, 0.5f, 1.0f, 1.0f), // Blue
998 ImVec4(1.0f, 0.5f, 0.0f, 1.0f), // Orange
999 ImVec4(1.0f, 0.0f, 1.0f, 1.0f), // Magenta
1000 ImVec4(1.0f, 1.0f, 0.0f, 1.0f), // Yellow
1001 ImVec4(0.0f, 1.0f, 1.0f, 1.0f), // Cyan
1002 ImVec4(1.0f, 0.0f, 0.0f, 1.0f), // Red
1003 ImVec4(0.5f, 0.5f, 0.5f, 1.0f), // Gray
1004 };
1005
1006 return colors[index % (sizeof(colors) / sizeof(colors[0]))];
1007}
1008
1009std::string SessionCoordinator::GetSessionIcon(size_t index) const {
1010 if (index >= sessions_.size())
1012
1013 const auto& session = sessions_[index];
1014
1015 if (session->rom.is_loaded()) {
1016 return ICON_MD_CHECK_CIRCLE;
1017 } else {
1019 }
1020}
1021
1022bool SessionCoordinator::IsSessionEmpty(size_t index) const {
1023 return IsValidSessionIndex(index) && !sessions_[index]->rom.is_loaded();
1024}
1025
1026bool SessionCoordinator::IsSessionClosed(size_t index) const {
1027 return !IsValidSessionIndex(index);
1028}
1029
1030bool SessionCoordinator::IsSessionModified(size_t index) const {
1031 // TODO: Implement modification tracking
1032 return false;
1033}
1034
1035} // namespace editor
1036} // namespace yaze
SessionCoordinator(WorkspaceWindowManager *window_manager, ToastManager *toast_manager, UserSettings *user_settings)
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_RADIO_BUTTON_CHECKED
Definition icons.h:1548
#define ICON_MD_TAB
Definition icons.h:1930
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_RADIO_BUTTON_UNCHECKED
Definition icons.h:1551
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_ANALYTICS
Definition icons.h:154
#define LOG_INFO(category, format,...)
Definition log.h:105
constexpr std::array< const char *, 14 > kEditorNames
Definition editor.h:221
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134