Refine XCEditor docking and DPI rendering
This commit is contained in:
@@ -1,12 +1,22 @@
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsPanelOpenAndVisible(
|
||||
const UIEditorWorkspaceSession& session,
|
||||
std::string_view panelId) {
|
||||
const UIEditorPanelSessionState* panelState =
|
||||
FindUIEditorPanelSessionState(session, panelId);
|
||||
return panelState != nullptr && panelState->open && panelState->visible;
|
||||
}
|
||||
|
||||
std::vector<std::string> CollectVisiblePanelIds(
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session) {
|
||||
@@ -22,6 +32,58 @@ std::vector<std::string> CollectVisiblePanelIds(
|
||||
return ids;
|
||||
}
|
||||
|
||||
struct VisibleTabStackInfo {
|
||||
bool panelExists = false;
|
||||
bool panelVisible = false;
|
||||
std::size_t currentVisibleIndex = 0u;
|
||||
std::size_t visibleTabCount = 0u;
|
||||
};
|
||||
|
||||
VisibleTabStackInfo ResolveVisibleTabStackInfo(
|
||||
const UIEditorWorkspaceNode& node,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
std::string_view panelId) {
|
||||
VisibleTabStackInfo info = {};
|
||||
for (const UIEditorWorkspaceNode& child : node.children) {
|
||||
if (child.kind != UIEditorWorkspaceNodeKind::Panel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool visible = IsPanelOpenAndVisible(session, child.panel.panelId);
|
||||
if (child.panel.panelId == panelId) {
|
||||
info.panelExists = true;
|
||||
info.panelVisible = visible;
|
||||
if (visible) {
|
||||
info.currentVisibleIndex = info.visibleTabCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
++info.visibleTabCount;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
std::size_t CountVisibleTabs(
|
||||
const UIEditorWorkspaceNode& node,
|
||||
const UIEditorWorkspaceSession& session) {
|
||||
if (node.kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
std::size_t visibleCount = 0u;
|
||||
for (const UIEditorWorkspaceNode& child : node.children) {
|
||||
if (child.kind == UIEditorWorkspaceNodeKind::Panel &&
|
||||
IsPanelOpenAndVisible(session, child.panel.panelId)) {
|
||||
++visibleCount;
|
||||
}
|
||||
}
|
||||
|
||||
return visibleCount;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string_view GetUIEditorWorkspaceCommandKindName(UIEditorWorkspaceCommandKind kind) {
|
||||
@@ -297,6 +359,290 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::SetSplitRati
|
||||
"Split ratio updated.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::ReorderTab(
|
||||
std::string_view nodeId,
|
||||
std::string_view panelId,
|
||||
std::size_t targetVisibleInsertionIndex) {
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Controller state invalid: " + validation.message);
|
||||
}
|
||||
|
||||
if (nodeId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab requires a tab stack node id.");
|
||||
}
|
||||
|
||||
if (panelId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab requires a panel id.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* tabStack = FindUIEditorWorkspaceNode(m_workspace, nodeId);
|
||||
if (tabStack == nullptr || tabStack->kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab target tab stack is missing.");
|
||||
}
|
||||
|
||||
const VisibleTabStackInfo tabInfo =
|
||||
ResolveVisibleTabStackInfo(*tabStack, m_session, panelId);
|
||||
if (!tabInfo.panelExists) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab target panel is missing from the specified tab stack.");
|
||||
}
|
||||
|
||||
if (!tabInfo.panelVisible) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab only supports open and visible tabs.");
|
||||
}
|
||||
|
||||
if (targetVisibleInsertionIndex > tabInfo.visibleTabCount) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab target visible insertion index is out of range.");
|
||||
}
|
||||
|
||||
if (targetVisibleInsertionIndex == tabInfo.currentVisibleIndex ||
|
||||
targetVisibleInsertionIndex == tabInfo.currentVisibleIndex + 1u) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Visible tab order already matches the requested insertion.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
if (!TryReorderUIEditorWorkspaceTab(
|
||||
m_workspace,
|
||||
m_session,
|
||||
nodeId,
|
||||
panelId,
|
||||
targetVisibleInsertionIndex)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Tab reorder rejected.");
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Visible tab order already matches the requested insertion.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState();
|
||||
if (!postValidation.IsValid()) {
|
||||
m_workspace = previousWorkspace;
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Tab reorder produced invalid controller state: " + postValidation.message);
|
||||
}
|
||||
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Changed,
|
||||
"Tab reordered.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToStack(
|
||||
std::string_view sourceNodeId,
|
||||
std::string_view panelId,
|
||||
std::string_view targetNodeId,
|
||||
std::size_t targetVisibleInsertionIndex) {
|
||||
{
|
||||
std::ostringstream trace = {};
|
||||
trace << "MoveTabToStack begin sourceNode=" << sourceNodeId
|
||||
<< " panel=" << panelId
|
||||
<< " targetNode=" << targetNodeId
|
||||
<< " insertion=" << targetVisibleInsertionIndex;
|
||||
AppendUIEditorRuntimeTrace("workspace", trace.str());
|
||||
}
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Controller state invalid: " + validation.message);
|
||||
}
|
||||
|
||||
if (sourceNodeId.empty() || targetNodeId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack requires both source and target tab stack ids.");
|
||||
}
|
||||
|
||||
if (panelId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack requires a panel id.");
|
||||
}
|
||||
|
||||
if (sourceNodeId == targetNodeId) {
|
||||
return ReorderTab(sourceNodeId, panelId, targetVisibleInsertionIndex);
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* sourceTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, sourceNodeId);
|
||||
const UIEditorWorkspaceNode* targetTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, targetNodeId);
|
||||
if (sourceTabStack == nullptr ||
|
||||
targetTabStack == nullptr ||
|
||||
sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack ||
|
||||
targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack source or target tab stack is missing.");
|
||||
}
|
||||
|
||||
const VisibleTabStackInfo sourceInfo =
|
||||
ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId);
|
||||
if (!sourceInfo.panelExists) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack target panel is missing from the source tab stack.");
|
||||
}
|
||||
|
||||
if (!sourceInfo.panelVisible) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack only supports open and visible tabs.");
|
||||
}
|
||||
|
||||
const std::size_t visibleTargetCount = CountVisibleTabs(*targetTabStack, m_session);
|
||||
if (targetVisibleInsertionIndex > visibleTargetCount) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack target visible insertion index is out of range.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
if (!TryMoveUIEditorWorkspaceTabToStack(
|
||||
m_workspace,
|
||||
m_session,
|
||||
sourceNodeId,
|
||||
panelId,
|
||||
targetNodeId,
|
||||
targetVisibleInsertionIndex)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack rejected.");
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Tab already matches the requested target stack insertion.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState();
|
||||
if (!postValidation.IsValid()) {
|
||||
m_workspace = previousWorkspace;
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack produced invalid controller state: " + postValidation.message);
|
||||
}
|
||||
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Changed,
|
||||
"Tab moved to target stack.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::DockTabRelative(
|
||||
std::string_view sourceNodeId,
|
||||
std::string_view panelId,
|
||||
std::string_view targetNodeId,
|
||||
UIEditorWorkspaceDockPlacement placement,
|
||||
float splitRatio) {
|
||||
{
|
||||
std::ostringstream trace = {};
|
||||
trace << "DockTabRelative begin sourceNode=" << sourceNodeId
|
||||
<< " panel=" << panelId
|
||||
<< " targetNode=" << targetNodeId
|
||||
<< " placement=" << static_cast<int>(placement)
|
||||
<< " splitRatio=" << splitRatio;
|
||||
AppendUIEditorRuntimeTrace("workspace", trace.str());
|
||||
}
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Controller state invalid: " + validation.message);
|
||||
}
|
||||
|
||||
if (sourceNodeId.empty() || targetNodeId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative requires both source and target tab stack ids.");
|
||||
}
|
||||
|
||||
if (panelId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative requires a panel id.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* sourceTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, sourceNodeId);
|
||||
const UIEditorWorkspaceNode* targetTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, targetNodeId);
|
||||
if (sourceTabStack == nullptr ||
|
||||
targetTabStack == nullptr ||
|
||||
sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack ||
|
||||
targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative source or target tab stack is missing.");
|
||||
}
|
||||
|
||||
const VisibleTabStackInfo sourceInfo =
|
||||
ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId);
|
||||
if (!sourceInfo.panelExists) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative target panel is missing from the source tab stack.");
|
||||
}
|
||||
|
||||
if (!sourceInfo.panelVisible) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative only supports open and visible tabs.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
if (!TryDockUIEditorWorkspaceTabRelative(
|
||||
m_workspace,
|
||||
m_session,
|
||||
sourceNodeId,
|
||||
panelId,
|
||||
targetNodeId,
|
||||
placement,
|
||||
splitRatio)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative rejected.");
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Dock layout already matches the requested placement.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState();
|
||||
if (!postValidation.IsValid()) {
|
||||
m_workspace = previousWorkspace;
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative produced invalid controller state: " + postValidation.message);
|
||||
}
|
||||
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Changed,
|
||||
"Tab docked relative to target stack.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceCommandResult UIEditorWorkspaceController::Dispatch(
|
||||
const UIEditorWorkspaceCommand& command) {
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
|
||||
Reference in New Issue
Block a user