Files
XCEngine/new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp

447 lines
16 KiB
C++

#include <XCEditor/Workspace/UIEditorWindowWorkspaceController.h>
#include <XCEditor/Workspace/UIEditorWorkspaceTransfer.h>
#include <algorithm>
#include <utility>
namespace XCEngine::UI::Editor {
namespace {
bool IsSinglePanelRootWindow(
const UIEditorWindowWorkspaceState& state,
std::string_view panelId) {
const UIEditorWorkspaceNode& root = state.workspace.root;
return root.kind == UIEditorWorkspaceNodeKind::TabStack &&
root.children.size() == 1u &&
root.children.front().kind == UIEditorWorkspaceNodeKind::Panel &&
root.children.front().panel.panelId == panelId &&
state.session.panelStates.size() == 1u &&
state.session.panelStates.front().panelId == panelId;
}
bool TryExtractPanelFromWindow(
UIEditorWindowWorkspaceSet& windowSet,
std::string_view sourceWindowId,
std::string_view primaryWindowId,
std::string_view sourceNodeId,
std::string_view panelId,
UIEditorWorkspaceExtractedPanel& extractedPanel) {
UIEditorWindowWorkspaceState* sourceWindow =
FindMutableUIEditorWindowWorkspaceState(windowSet, sourceWindowId);
if (sourceWindow == nullptr) {
return false;
}
if (sourceWindowId != primaryWindowId &&
IsSinglePanelRootWindow(*sourceWindow, panelId)) {
extractedPanel.panelNode = std::move(sourceWindow->workspace.root.children.front());
extractedPanel.sessionState = sourceWindow->session.panelStates.front();
windowSet.windows.erase(
std::remove_if(
windowSet.windows.begin(),
windowSet.windows.end(),
[sourceWindowId](const UIEditorWindowWorkspaceState& state) {
return state.windowId == sourceWindowId;
}),
windowSet.windows.end());
return true;
}
return TryExtractUIEditorWorkspaceVisiblePanel(
sourceWindow->workspace,
sourceWindow->session,
sourceNodeId,
panelId,
extractedPanel);
}
} // namespace
std::string_view GetUIEditorWindowWorkspaceOperationStatusName(
UIEditorWindowWorkspaceOperationStatus status) {
switch (status) {
case UIEditorWindowWorkspaceOperationStatus::Changed:
return "Changed";
case UIEditorWindowWorkspaceOperationStatus::NoOp:
return "NoOp";
case UIEditorWindowWorkspaceOperationStatus::Rejected:
return "Rejected";
}
return "Unknown";
}
UIEditorWindowWorkspaceController::UIEditorWindowWorkspaceController(
UIEditorPanelRegistry panelRegistry,
UIEditorWindowWorkspaceSet windowSet)
: m_panelRegistry(std::move(panelRegistry))
, m_windowSet(std::move(windowSet)) {
}
UIEditorWindowWorkspaceValidationResult UIEditorWindowWorkspaceController::ValidateState() const {
return ValidateUIEditorWindowWorkspaceSet(m_panelRegistry, m_windowSet);
}
UIEditorWindowWorkspaceOperationResult UIEditorWindowWorkspaceController::BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus status,
std::string message,
std::string_view sourceWindowId,
std::string_view targetWindowId,
std::string_view panelId) const {
UIEditorWindowWorkspaceOperationResult result = {};
result.status = status;
result.message = std::move(message);
result.sourceWindowId = std::string(sourceWindowId);
result.targetWindowId = std::string(targetWindowId);
result.panelId = std::string(panelId);
result.activeWindowId = m_windowSet.activeWindowId;
result.windowIds.reserve(m_windowSet.windows.size());
for (const UIEditorWindowWorkspaceState& state : m_windowSet.windows) {
result.windowIds.push_back(state.windowId);
}
return result;
}
std::string UIEditorWindowWorkspaceController::MakeUniqueWindowId(std::string_view base) const {
std::string resolvedBase = base.empty()
? std::string("detached-window")
: std::string(base);
if (FindUIEditorWindowWorkspaceState(m_windowSet, resolvedBase) == nullptr) {
return resolvedBase;
}
for (std::size_t suffix = 1u; suffix < 1024u; ++suffix) {
const std::string candidate = resolvedBase + "-" + std::to_string(suffix);
if (FindUIEditorWindowWorkspaceState(m_windowSet, candidate) == nullptr) {
return candidate;
}
}
return resolvedBase + "-overflow";
}
UIEditorWindowWorkspaceOperationResult UIEditorWindowWorkspaceController::DetachPanelToNewWindow(
std::string_view sourceWindowId,
std::string_view sourceNodeId,
std::string_view panelId,
std::string_view preferredNewWindowId) {
const UIEditorWindowWorkspaceValidationResult stateValidation = ValidateState();
if (!stateValidation.IsValid()) {
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Window workspace state invalid: " + stateValidation.message,
sourceWindowId,
{},
panelId);
}
const UIEditorWindowWorkspaceState* sourceWindow =
FindUIEditorWindowWorkspaceState(m_windowSet, sourceWindowId);
if (sourceWindow == nullptr) {
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Source window not found.",
sourceWindowId,
{},
panelId);
}
if (sourceWindowId != m_windowSet.primaryWindowId &&
IsSinglePanelRootWindow(*sourceWindow, panelId)) {
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::NoOp,
"Panel already occupies its own detached window.",
sourceWindowId,
sourceWindowId,
panelId);
}
const UIEditorWindowWorkspaceSet windowSetBefore = m_windowSet;
UIEditorWorkspaceExtractedPanel extractedPanel = {};
if (!TryExtractPanelFromWindow(
m_windowSet,
sourceWindowId,
m_windowSet.primaryWindowId,
sourceNodeId,
panelId,
extractedPanel)) {
m_windowSet = windowSetBefore;
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Failed to extract panel from source window.",
sourceWindowId,
{},
panelId);
}
const std::string newWindowId = MakeUniqueWindowId(
preferredNewWindowId.empty()
? std::string(panelId) + "-window"
: std::string(preferredNewWindowId));
UIEditorWindowWorkspaceState detachedWindow = {};
detachedWindow.windowId = newWindowId;
detachedWindow.workspace =
BuildUIEditorDetachedWorkspaceFromExtractedPanel(
newWindowId + "-root",
extractedPanel);
detachedWindow.session =
BuildUIEditorDetachedWorkspaceSessionFromExtractedPanel(extractedPanel);
m_windowSet.windows.push_back(std::move(detachedWindow));
m_windowSet.activeWindowId = newWindowId;
const UIEditorWindowWorkspaceValidationResult validation = ValidateState();
if (!validation.IsValid()) {
m_windowSet = windowSetBefore;
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Detach produced invalid state: " + validation.message,
sourceWindowId,
newWindowId,
panelId);
}
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Changed,
"Panel detached into a new window.",
sourceWindowId,
newWindowId,
panelId);
}
UIEditorWindowWorkspaceOperationResult UIEditorWindowWorkspaceController::MovePanelToStack(
std::string_view sourceWindowId,
std::string_view sourceNodeId,
std::string_view panelId,
std::string_view targetWindowId,
std::string_view targetNodeId,
std::size_t targetVisibleInsertionIndex) {
const UIEditorWindowWorkspaceValidationResult stateValidation = ValidateState();
if (!stateValidation.IsValid()) {
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Window workspace state invalid: " + stateValidation.message,
sourceWindowId,
targetWindowId,
panelId);
}
if (sourceWindowId == targetWindowId) {
UIEditorWindowWorkspaceState* window =
FindMutableUIEditorWindowWorkspaceState(m_windowSet, sourceWindowId);
if (window == nullptr) {
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Source window not found.",
sourceWindowId,
targetWindowId,
panelId);
}
if (!TryMoveUIEditorWorkspaceTabToStack(
window->workspace,
window->session,
sourceNodeId,
panelId,
targetNodeId,
targetVisibleInsertionIndex)) {
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Move operation rejected by the workspace model.",
sourceWindowId,
targetWindowId,
panelId);
}
m_windowSet.activeWindowId = std::string(targetWindowId);
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Changed,
"Panel moved within the same window.",
sourceWindowId,
targetWindowId,
panelId);
}
const UIEditorWindowWorkspaceSet windowSetBefore = m_windowSet;
UIEditorWorkspaceExtractedPanel extractedPanel = {};
if (!TryExtractPanelFromWindow(
m_windowSet,
sourceWindowId,
m_windowSet.primaryWindowId,
sourceNodeId,
panelId,
extractedPanel)) {
m_windowSet = windowSetBefore;
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Failed to extract panel from source window.",
sourceWindowId,
targetWindowId,
panelId);
}
UIEditorWindowWorkspaceState* targetWindow =
FindMutableUIEditorWindowWorkspaceState(m_windowSet, targetWindowId);
if (targetWindow == nullptr ||
!TryInsertExtractedUIEditorWorkspacePanelToStack(
targetWindow->workspace,
targetWindow->session,
std::move(extractedPanel),
targetNodeId,
targetVisibleInsertionIndex)) {
m_windowSet = windowSetBefore;
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Failed to insert the extracted panel into the target stack.",
sourceWindowId,
targetWindowId,
panelId);
}
m_windowSet.activeWindowId = std::string(targetWindowId);
const UIEditorWindowWorkspaceValidationResult validation = ValidateState();
if (!validation.IsValid()) {
m_windowSet = windowSetBefore;
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Move produced invalid state: " + validation.message,
sourceWindowId,
targetWindowId,
panelId);
}
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Changed,
"Panel moved across windows.",
sourceWindowId,
targetWindowId,
panelId);
}
UIEditorWindowWorkspaceOperationResult UIEditorWindowWorkspaceController::DockPanelRelative(
std::string_view sourceWindowId,
std::string_view sourceNodeId,
std::string_view panelId,
std::string_view targetWindowId,
std::string_view targetNodeId,
UIEditorWorkspaceDockPlacement placement,
float splitRatio) {
const UIEditorWindowWorkspaceValidationResult stateValidation = ValidateState();
if (!stateValidation.IsValid()) {
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Window workspace state invalid: " + stateValidation.message,
sourceWindowId,
targetWindowId,
panelId);
}
if (sourceWindowId == targetWindowId) {
UIEditorWindowWorkspaceState* window =
FindMutableUIEditorWindowWorkspaceState(m_windowSet, sourceWindowId);
if (window == nullptr) {
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Source window not found.",
sourceWindowId,
targetWindowId,
panelId);
}
if (!TryDockUIEditorWorkspaceTabRelative(
window->workspace,
window->session,
sourceNodeId,
panelId,
targetNodeId,
placement,
splitRatio)) {
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Dock operation rejected by the workspace model.",
sourceWindowId,
targetWindowId,
panelId);
}
m_windowSet.activeWindowId = std::string(targetWindowId);
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Changed,
"Panel docked within the same window.",
sourceWindowId,
targetWindowId,
panelId);
}
const UIEditorWindowWorkspaceSet windowSetBefore = m_windowSet;
UIEditorWorkspaceExtractedPanel extractedPanel = {};
if (!TryExtractPanelFromWindow(
m_windowSet,
sourceWindowId,
m_windowSet.primaryWindowId,
sourceNodeId,
panelId,
extractedPanel)) {
m_windowSet = windowSetBefore;
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Failed to extract panel from source window.",
sourceWindowId,
targetWindowId,
panelId);
}
UIEditorWindowWorkspaceState* targetWindow =
FindMutableUIEditorWindowWorkspaceState(m_windowSet, targetWindowId);
if (targetWindow == nullptr ||
!TryDockExtractedUIEditorWorkspacePanelRelative(
targetWindow->workspace,
targetWindow->session,
std::move(extractedPanel),
targetNodeId,
placement,
splitRatio)) {
m_windowSet = windowSetBefore;
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Failed to dock the extracted panel into the target window.",
sourceWindowId,
targetWindowId,
panelId);
}
m_windowSet.activeWindowId = std::string(targetWindowId);
const UIEditorWindowWorkspaceValidationResult validation = ValidateState();
if (!validation.IsValid()) {
m_windowSet = windowSetBefore;
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Rejected,
"Dock produced invalid state: " + validation.message,
sourceWindowId,
targetWindowId,
panelId);
}
return BuildOperationResult(
UIEditorWindowWorkspaceOperationStatus::Changed,
"Panel docked across windows.",
sourceWindowId,
targetWindowId,
panelId);
}
UIEditorWindowWorkspaceController BuildDefaultUIEditorWindowWorkspaceController(
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace,
std::string primaryWindowId) {
return UIEditorWindowWorkspaceController(
panelRegistry,
BuildDefaultUIEditorWindowWorkspaceSet(
panelRegistry,
workspace,
std::move(primaryWindowId)));
}
} // namespace XCEngine::UI::Editor