关键节点

This commit is contained in:
2026-04-25 16:46:01 +08:00
parent 6002d86a7e
commit ef41c44464
516 changed files with 6175 additions and 12401 deletions

View File

@@ -0,0 +1,244 @@
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
#include <utility>
namespace XCEngine::UI::Editor {
namespace {
UIEditorCommandEvaluationResult MakeEvaluationResult(
UIEditorCommandEvaluationCode code,
bool executable,
std::string commandId,
std::string displayName,
UIEditorWorkspaceCommand workspaceCommand,
UIEditorWorkspaceCommandResult previewResult,
std::string message,
UIEditorCommandKind kind) {
UIEditorCommandEvaluationResult result = {};
result.code = code;
result.executable = executable;
result.commandId = std::move(commandId);
result.displayName = std::move(displayName);
result.workspaceCommand = std::move(workspaceCommand);
result.previewResult = std::move(previewResult);
result.message = std::move(message);
result.kind = kind;
return result;
}
UIEditorCommandDispatchResult BuildDispatchResult(
UIEditorCommandDispatchStatus status,
bool commandExecuted,
std::string commandId,
std::string displayName,
UIEditorWorkspaceCommand workspaceCommand,
UIEditorWorkspaceCommandResult commandResult,
std::string message,
UIEditorCommandKind kind) {
UIEditorCommandDispatchResult result = {};
result.status = status;
result.commandExecuted = commandExecuted;
result.commandId = std::move(commandId);
result.displayName = std::move(displayName);
result.workspaceCommand = std::move(workspaceCommand);
result.commandResult = std::move(commandResult);
result.message = std::move(message);
result.kind = kind;
return result;
}
} // namespace
std::string_view GetUIEditorCommandDispatchStatusName(
UIEditorCommandDispatchStatus status) {
switch (status) {
case UIEditorCommandDispatchStatus::Dispatched:
return "Dispatched";
case UIEditorCommandDispatchStatus::Rejected:
return "Rejected";
}
return "Unknown";
}
UIEditorCommandDispatcher::UIEditorCommandDispatcher(
UIEditorCommandRegistry commandRegistry)
: m_commandRegistry(std::move(commandRegistry)) {
}
UIEditorCommandRegistryValidationResult
UIEditorCommandDispatcher::ValidateConfiguration() const {
return ValidateUIEditorCommandRegistry(m_commandRegistry);
}
UIEditorCommandEvaluationResult UIEditorCommandDispatcher::Evaluate(
std::string_view commandId,
const UIEditorWorkspaceController& controller) const {
const auto validation = ValidateConfiguration();
if (!validation.IsValid()) {
return MakeEvaluationResult(
UIEditorCommandEvaluationCode::InvalidCommandRegistry,
false,
std::string(commandId),
{},
{},
{},
"Command registry invalid: " + validation.message,
UIEditorCommandKind::Workspace);
}
const UIEditorCommandDescriptor* descriptor =
FindUIEditorCommandDescriptor(m_commandRegistry, commandId);
if (descriptor == nullptr) {
return MakeEvaluationResult(
UIEditorCommandEvaluationCode::UnknownCommandId,
false,
std::string(commandId),
{},
{},
{},
"Editor command '" + std::string(commandId) + "' is not registered.",
UIEditorCommandKind::Workspace);
}
if (descriptor->kind == UIEditorCommandKind::Host) {
if (m_hostCommandHandler == nullptr) {
return MakeEvaluationResult(
UIEditorCommandEvaluationCode::MissingHostCommandHandler,
false,
descriptor->commandId,
descriptor->displayName,
{},
{},
"Host command handler is not attached for '" + descriptor->commandId + "'.",
descriptor->kind);
}
const UIEditorHostCommandEvaluationResult hostEvaluation =
m_hostCommandHandler->EvaluateHostCommand(descriptor->commandId);
return MakeEvaluationResult(
hostEvaluation.executable
? UIEditorCommandEvaluationCode::None
: UIEditorCommandEvaluationCode::HostCommandDisabled,
hostEvaluation.executable,
descriptor->commandId,
descriptor->displayName,
{},
{},
hostEvaluation.message.empty()
? std::string("Host command evaluated.")
: hostEvaluation.message,
descriptor->kind);
}
UIEditorWorkspaceCommand workspaceCommand = {};
workspaceCommand.kind = descriptor->workspaceCommand.kind;
switch (descriptor->workspaceCommand.panelSource) {
case UIEditorCommandPanelSource::None:
break;
case UIEditorCommandPanelSource::FixedPanelId:
workspaceCommand.panelId = descriptor->workspaceCommand.panelId;
break;
case UIEditorCommandPanelSource::ActivePanel:
if (controller.GetWorkspace().activePanelId.empty()) {
return MakeEvaluationResult(
UIEditorCommandEvaluationCode::MissingActivePanel,
false,
descriptor->commandId,
descriptor->displayName,
{},
{},
"Editor command '" + descriptor->commandId + "' requires an active panel.",
descriptor->kind);
}
workspaceCommand.panelId = controller.GetWorkspace().activePanelId;
break;
}
UIEditorWorkspaceController previewController = controller;
UIEditorWorkspaceCommandResult previewResult =
previewController.Dispatch(workspaceCommand);
return MakeEvaluationResult(
UIEditorCommandEvaluationCode::None,
previewResult.status != UIEditorWorkspaceCommandStatus::Rejected,
descriptor->commandId,
descriptor->displayName,
std::move(workspaceCommand),
std::move(previewResult),
"Editor command resolved.",
descriptor->kind);
}
UIEditorCommandDispatchResult UIEditorCommandDispatcher::Dispatch(
std::string_view commandId,
UIEditorWorkspaceController& controller) const {
const UIEditorCommandEvaluationResult evaluation =
Evaluate(commandId, controller);
if (!evaluation.IsExecutable()) {
return BuildDispatchResult(
UIEditorCommandDispatchStatus::Rejected,
false,
evaluation.commandId,
evaluation.displayName,
evaluation.workspaceCommand,
evaluation.previewResult,
evaluation.message,
evaluation.kind);
}
if (evaluation.kind == UIEditorCommandKind::Host) {
if (m_hostCommandHandler == nullptr) {
return BuildDispatchResult(
UIEditorCommandDispatchStatus::Rejected,
false,
evaluation.commandId,
evaluation.displayName,
{},
{},
"Host command handler is not attached.",
evaluation.kind);
}
const UIEditorHostCommandDispatchResult hostDispatch =
m_hostCommandHandler->DispatchHostCommand(evaluation.commandId);
return BuildDispatchResult(
hostDispatch.commandExecuted
? UIEditorCommandDispatchStatus::Dispatched
: UIEditorCommandDispatchStatus::Rejected,
hostDispatch.commandExecuted,
evaluation.commandId,
evaluation.displayName,
{},
{},
hostDispatch.message.empty()
? std::string("Host command dispatched.")
: hostDispatch.message,
evaluation.kind);
}
UIEditorWorkspaceCommandResult commandResult =
controller.Dispatch(evaluation.workspaceCommand);
const bool commandExecuted =
commandResult.status != UIEditorWorkspaceCommandStatus::Rejected;
return BuildDispatchResult(
commandExecuted
? UIEditorCommandDispatchStatus::Dispatched
: UIEditorCommandDispatchStatus::Rejected,
commandExecuted,
evaluation.commandId,
evaluation.displayName,
evaluation.workspaceCommand,
std::move(commandResult),
commandExecuted
? "Editor command dispatched."
: "Editor command dispatch was rejected.",
evaluation.kind);
}
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,152 @@
#include <XCEditor/Foundation/UIEditorCommandRegistry.h>
#include <unordered_set>
#include <utility>
namespace XCEngine::UI::Editor {
namespace {
UIEditorCommandRegistryValidationResult MakeValidationError(
UIEditorCommandRegistryValidationCode code,
std::string message) {
UIEditorCommandRegistryValidationResult result = {};
result.code = code;
result.message = std::move(message);
return result;
}
bool CommandKindRequiresPanelId(UIEditorWorkspaceCommandKind kind) {
switch (kind) {
case UIEditorWorkspaceCommandKind::OpenPanel:
case UIEditorWorkspaceCommandKind::ClosePanel:
case UIEditorWorkspaceCommandKind::ShowPanel:
case UIEditorWorkspaceCommandKind::HidePanel:
case UIEditorWorkspaceCommandKind::ActivatePanel:
return true;
case UIEditorWorkspaceCommandKind::ResetWorkspace:
return false;
}
return false;
}
} // namespace
std::string_view GetUIEditorCommandPanelSourceName(UIEditorCommandPanelSource source) {
switch (source) {
case UIEditorCommandPanelSource::None:
return "None";
case UIEditorCommandPanelSource::FixedPanelId:
return "FixedPanelId";
case UIEditorCommandPanelSource::ActivePanel:
return "ActivePanel";
}
return "Unknown";
}
std::string_view GetUIEditorCommandKindName(UIEditorCommandKind kind) {
switch (kind) {
case UIEditorCommandKind::Workspace:
return "Workspace";
case UIEditorCommandKind::Host:
return "Host";
}
return "Unknown";
}
const UIEditorCommandDescriptor* FindUIEditorCommandDescriptor(
const UIEditorCommandRegistry& registry,
std::string_view commandId) {
for (const UIEditorCommandDescriptor& command : registry.commands) {
if (command.commandId == commandId) {
return &command;
}
}
return nullptr;
}
UIEditorCommandRegistryValidationResult ValidateUIEditorCommandRegistry(
const UIEditorCommandRegistry& registry) {
std::unordered_set<std::string> seenCommandIds = {};
for (const UIEditorCommandDescriptor& command : registry.commands) {
if (command.commandId.empty()) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::EmptyCommandId,
"Editor command id must not be empty.");
}
if (command.displayName.empty()) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::EmptyDisplayName,
"Editor command '" + command.commandId + "' must define a displayName.");
}
if (!seenCommandIds.insert(command.commandId).second) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::DuplicateCommandId,
"Editor command id '" + command.commandId + "' is duplicated.");
}
if (command.kind == UIEditorCommandKind::Host) {
if (command.workspaceCommand.panelSource != UIEditorCommandPanelSource::None ||
!command.workspaceCommand.panelId.empty()) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::UnexpectedPanelSource,
"Host editor command '" + command.commandId +
"' must not define workspace panel routing.");
}
continue;
}
const bool requiresPanelId =
CommandKindRequiresPanelId(command.workspaceCommand.kind);
switch (command.workspaceCommand.panelSource) {
case UIEditorCommandPanelSource::None:
if (requiresPanelId) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::MissingPanelSource,
"Editor command '" + command.commandId + "' requires a panel source.");
}
if (!command.workspaceCommand.panelId.empty()) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::UnexpectedPanelSource,
"Editor command '" + command.commandId + "' must not carry a fixed panel id.");
}
break;
case UIEditorCommandPanelSource::FixedPanelId:
if (!requiresPanelId) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::UnexpectedPanelSource,
"Editor command '" + command.commandId + "' must not use a fixed panel id.");
}
if (command.workspaceCommand.panelId.empty()) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::MissingFixedPanelId,
"Editor command '" + command.commandId + "' fixed panel source requires a panel id.");
}
break;
case UIEditorCommandPanelSource::ActivePanel:
if (!requiresPanelId) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::UnexpectedPanelSource,
"Editor command '" + command.commandId + "' must not use the active panel source.");
}
if (!command.workspaceCommand.panelId.empty()) {
return MakeValidationError(
UIEditorCommandRegistryValidationCode::UnexpectedPanelSource,
"Editor command '" + command.commandId + "' active panel source must not carry a fixed panel id.");
}
break;
}
}
return {};
}
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,356 @@
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
#include <windows.h>
#include <dbghelp.h>
#include <chrono>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <mutex>
#include <sstream>
#include <string>
#pragma comment(lib, "Dbghelp.lib")
namespace XCEngine::UI::Editor {
namespace {
std::mutex g_traceMutex = {};
std::filesystem::path g_logRoot = {};
std::filesystem::path g_runtimeTracePath = {};
std::filesystem::path g_crashTracePath = {};
std::ofstream g_runtimeTraceStream = {};
std::ofstream g_crashTraceStream = {};
bool g_traceInitialized = false;
bool g_symbolHandlerInitialized = false;
struct CrashStackFrame {
DWORD64 address = 0u;
DWORD64 displacement = 0u;
DWORD line = 0u;
DWORD lineDisplacement = 0u;
std::string moduleName = {};
std::string symbolName = {};
std::string sourceFile = {};
};
std::string BuildTimestampString() {
const auto now = std::chrono::system_clock::now();
const std::time_t currentTime = std::chrono::system_clock::to_time_t(now);
std::tm localTime = {};
localtime_s(&localTime, &currentTime);
const auto milliseconds =
std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) %
1000;
std::ostringstream stream = {};
stream << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S")
<< '.'
<< std::setw(3)
<< std::setfill('0')
<< milliseconds.count();
return stream.str();
}
void AppendTraceLine(
std::ofstream& stream,
std::string_view channel,
std::string_view message) {
if (!stream.is_open()) {
return;
}
stream << '['
<< BuildTimestampString()
<< "] ["
<< channel
<< "] "
<< message
<< '\n';
}
std::string NarrowWideString(std::wstring_view text) {
if (text.empty()) {
return {};
}
const int sizeNeeded = WideCharToMultiByte(
CP_UTF8,
0,
text.data(),
static_cast<int>(text.size()),
nullptr,
0,
nullptr,
nullptr);
if (sizeNeeded <= 0) {
return {};
}
std::string result(static_cast<std::size_t>(sizeNeeded), '\0');
WideCharToMultiByte(
CP_UTF8,
0,
text.data(),
static_cast<int>(text.size()),
result.data(),
sizeNeeded,
nullptr,
nullptr);
return result;
}
std::string FormatPointer(const void* address) {
char buffer[32] = {};
std::snprintf(buffer, sizeof(buffer), "%p", address);
return buffer;
}
std::string ResolveModulePathFromAddress(DWORD64 address) {
if (address == 0u) {
return {};
}
HMODULE moduleHandle = nullptr;
if (!GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(address),
&moduleHandle) ||
moduleHandle == nullptr) {
return {};
}
wchar_t modulePath[MAX_PATH] = {};
const DWORD length = GetModuleFileNameW(moduleHandle, modulePath, MAX_PATH);
if (length == 0u) {
return {};
}
return NarrowWideString(std::wstring_view(modulePath, length));
}
void EnsureSymbolHandlerInitialized(HANDLE process) {
if (g_symbolHandlerInitialized) {
return;
}
SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_LOAD_LINES);
g_symbolHandlerInitialized = SymInitialize(process, nullptr, TRUE) == TRUE;
}
std::vector<CrashStackFrame> CaptureExceptionStack(const _EXCEPTION_POINTERS* exceptionPointers) {
std::vector<CrashStackFrame> frames = {};
if (exceptionPointers == nullptr || exceptionPointers->ContextRecord == nullptr) {
return frames;
}
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
EnsureSymbolHandlerInitialized(process);
CONTEXT context = *exceptionPointers->ContextRecord;
STACKFRAME64 stackFrame = {};
DWORD machineType = 0u;
#if defined(_M_X64)
machineType = IMAGE_FILE_MACHINE_AMD64;
stackFrame.AddrPC.Offset = context.Rip;
stackFrame.AddrFrame.Offset = context.Rbp;
stackFrame.AddrStack.Offset = context.Rsp;
#elif defined(_M_IX86)
machineType = IMAGE_FILE_MACHINE_I386;
stackFrame.AddrPC.Offset = context.Eip;
stackFrame.AddrFrame.Offset = context.Ebp;
stackFrame.AddrStack.Offset = context.Esp;
#else
return frames;
#endif
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrStack.Mode = AddrModeFlat;
constexpr std::size_t kMaxFrames = 32u;
frames.reserve(kMaxFrames);
for (std::size_t frameIndex = 0u; frameIndex < kMaxFrames; ++frameIndex) {
const BOOL advanced = StackWalk64(
machineType,
process,
thread,
&stackFrame,
&context,
nullptr,
SymFunctionTableAccess64,
SymGetModuleBase64,
nullptr);
if (advanced == FALSE || stackFrame.AddrPC.Offset == 0u) {
break;
}
CrashStackFrame frame = {};
frame.address = stackFrame.AddrPC.Offset;
frame.moduleName = ResolveModulePathFromAddress(frame.address);
std::byte symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME] = {};
SYMBOL_INFO* symbol = reinterpret_cast<SYMBOL_INFO*>(symbolBuffer);
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;
if (g_symbolHandlerInitialized &&
SymFromAddr(process, frame.address, &frame.displacement, symbol) == TRUE) {
frame.symbolName = symbol->Name;
}
IMAGEHLP_LINE64 line = {};
line.SizeOfStruct = sizeof(line);
if (g_symbolHandlerInitialized &&
SymGetLineFromAddr64(process, frame.address, &frame.lineDisplacement, &line) == TRUE) {
frame.line = line.LineNumber;
if (line.FileName != nullptr) {
frame.sourceFile = line.FileName;
}
}
frames.push_back(std::move(frame));
}
return frames;
}
std::string FormatCrashStackFrame(const CrashStackFrame& frame, std::size_t index) {
std::ostringstream stream = {};
stream << "stack[" << index << "] pc=0x"
<< std::hex << std::uppercase << frame.address << std::dec;
if (!frame.moduleName.empty()) {
stream << " module=" << frame.moduleName;
}
if (!frame.symbolName.empty()) {
stream << " symbol=" << frame.symbolName;
if (frame.displacement != 0u) {
stream << "+0x" << std::hex << std::uppercase << frame.displacement << std::dec;
}
}
if (!frame.sourceFile.empty()) {
stream << " source=" << frame.sourceFile;
if (frame.line != 0u) {
stream << ':' << frame.line;
if (frame.lineDisplacement != 0u) {
stream << "+0x" << std::hex << std::uppercase
<< frame.lineDisplacement << std::dec;
}
}
}
return stream.str();
}
} // namespace
void InitializeUIEditorRuntimeTrace(const std::filesystem::path& logRoot) {
std::lock_guard lock(g_traceMutex);
g_logRoot = logRoot.lexically_normal();
g_runtimeTracePath = (g_logRoot / "runtime.log").lexically_normal();
g_crashTracePath = (g_logRoot / "crash.log").lexically_normal();
std::error_code errorCode = {};
std::filesystem::create_directories(g_logRoot, errorCode);
g_runtimeTraceStream.close();
g_crashTraceStream.close();
g_runtimeTraceStream.open(g_runtimeTracePath, std::ios::out | std::ios::app);
g_crashTraceStream.open(g_crashTracePath, std::ios::out | std::ios::app);
g_traceInitialized = g_runtimeTraceStream.is_open() && g_crashTraceStream.is_open();
if (!g_traceInitialized) {
return;
}
AppendTraceLine(g_runtimeTraceStream, "trace", "trace session started");
g_runtimeTraceStream.flush();
}
void ShutdownUIEditorRuntimeTrace() {
std::lock_guard lock(g_traceMutex);
if (!g_traceInitialized) {
return;
}
AppendTraceLine(g_runtimeTraceStream, "trace", "trace session ended");
g_runtimeTraceStream.flush();
g_crashTraceStream.flush();
g_runtimeTraceStream.close();
g_crashTraceStream.close();
g_traceInitialized = false;
}
void AppendUIEditorRuntimeTrace(
std::string_view channel,
std::string_view message) {
std::lock_guard lock(g_traceMutex);
if (!g_traceInitialized) {
return;
}
AppendTraceLine(g_runtimeTraceStream, channel, message);
g_runtimeTraceStream.flush();
}
void AppendUIEditorCrashTrace(
std::uint32_t exceptionCode,
const void* exceptionAddress) {
AppendUIEditorCrashTrace(exceptionCode, exceptionAddress, nullptr);
}
void AppendUIEditorCrashTrace(
std::uint32_t exceptionCode,
const void* exceptionAddress,
const _EXCEPTION_POINTERS* exceptionPointers) {
std::lock_guard lock(g_traceMutex);
if (!g_traceInitialized) {
return;
}
std::ostringstream header = {};
header << "Unhandled exception code=0x"
<< std::hex << std::uppercase << std::setw(8) << std::setfill('0')
<< exceptionCode
<< std::dec << std::setfill(' ')
<< " address=" << FormatPointer(exceptionAddress);
const std::string exceptionModule =
ResolveModulePathFromAddress(reinterpret_cast<DWORD64>(exceptionAddress));
if (!exceptionModule.empty()) {
header << " module=" << exceptionModule;
}
header << " threadId=" << GetCurrentThreadId();
const std::string headerText = header.str();
AppendTraceLine(g_crashTraceStream, "crash", headerText);
AppendTraceLine(g_runtimeTraceStream, "crash", headerText);
const std::vector<CrashStackFrame> frames = CaptureExceptionStack(exceptionPointers);
for (std::size_t index = 0u; index < frames.size(); ++index) {
const std::string line = FormatCrashStackFrame(frames[index], index);
AppendTraceLine(g_crashTraceStream, "crash", line);
AppendTraceLine(g_runtimeTraceStream, "crash", line);
}
g_crashTraceStream.flush();
g_runtimeTraceStream.flush();
}
std::filesystem::path GetUIEditorRuntimeTracePath() {
std::lock_guard lock(g_traceMutex);
return g_runtimeTracePath;
}
std::filesystem::path GetUIEditorCrashTracePath() {
std::lock_guard lock(g_traceMutex);
return g_crashTracePath;
}
bool IsUIEditorRuntimeTraceInitialized() {
std::lock_guard lock(g_traceMutex);
return g_traceInitialized;
}
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,344 @@
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include <XCEngine/Input/InputTypes.h>
#include <utility>
namespace XCEngine::UI::Editor {
namespace {
using XCEngine::UI::UIShortcutBinding;
using XCEngine::UI::UIShortcutMatch;
using XCEngine::UI::UIShortcutScope;
using XCEngine::Input::KeyCode;
UIEditorShortcutManagerValidationResult MakeValidationError(
UIEditorShortcutManagerValidationCode code,
std::string message) {
UIEditorShortcutManagerValidationResult result = {};
result.code = code;
result.message = std::move(message);
return result;
}
bool ModifiersEqual(
const XCEngine::UI::UIInputModifiers& lhs,
const XCEngine::UI::UIInputModifiers& rhs) {
return lhs.shift == rhs.shift &&
lhs.control == rhs.control &&
lhs.alt == rhs.alt &&
lhs.super == rhs.super;
}
bool HaveConflictingChord(
const UIShortcutBinding& lhs,
const UIShortcutBinding& rhs) {
return lhs.triggerEventType == rhs.triggerEventType &&
lhs.scope == rhs.scope &&
lhs.ownerId == rhs.ownerId &&
lhs.chord.keyCode == rhs.chord.keyCode &&
ModifiersEqual(lhs.chord.modifiers, rhs.chord.modifiers);
}
int ShortcutDisplayPriority(UIShortcutScope scope) {
switch (scope) {
case UIShortcutScope::Global:
return 0;
case UIShortcutScope::Window:
return 1;
case UIShortcutScope::Panel:
return 2;
case UIShortcutScope::Widget:
return 3;
}
return 4;
}
std::string GetKeyCodeDisplayName(std::int32_t keyCode) {
if (keyCode >= static_cast<std::int32_t>(KeyCode::A) &&
keyCode <= static_cast<std::int32_t>(KeyCode::Z)) {
return std::string(1, static_cast<char>('A' +
(keyCode - static_cast<std::int32_t>(KeyCode::A))));
}
if (keyCode == static_cast<std::int32_t>(KeyCode::Zero)) return "0";
if (keyCode == static_cast<std::int32_t>(KeyCode::One)) return "1";
if (keyCode == static_cast<std::int32_t>(KeyCode::Two)) return "2";
if (keyCode == static_cast<std::int32_t>(KeyCode::Three)) return "3";
if (keyCode == static_cast<std::int32_t>(KeyCode::Four)) return "4";
if (keyCode == static_cast<std::int32_t>(KeyCode::Five)) return "5";
if (keyCode == static_cast<std::int32_t>(KeyCode::Six)) return "6";
if (keyCode == static_cast<std::int32_t>(KeyCode::Seven)) return "7";
if (keyCode == static_cast<std::int32_t>(KeyCode::Eight)) return "8";
if (keyCode == static_cast<std::int32_t>(KeyCode::Nine)) return "9";
if (keyCode == static_cast<std::int32_t>(KeyCode::F1)) return "F1";
if (keyCode == static_cast<std::int32_t>(KeyCode::F2)) return "F2";
if (keyCode == static_cast<std::int32_t>(KeyCode::F3)) return "F3";
if (keyCode == static_cast<std::int32_t>(KeyCode::F4)) return "F4";
if (keyCode == static_cast<std::int32_t>(KeyCode::F5)) return "F5";
if (keyCode == static_cast<std::int32_t>(KeyCode::F6)) return "F6";
if (keyCode == static_cast<std::int32_t>(KeyCode::F7)) return "F7";
if (keyCode == static_cast<std::int32_t>(KeyCode::F8)) return "F8";
if (keyCode == static_cast<std::int32_t>(KeyCode::F9)) return "F9";
if (keyCode == static_cast<std::int32_t>(KeyCode::F10)) return "F10";
if (keyCode == static_cast<std::int32_t>(KeyCode::F11)) return "F11";
if (keyCode == static_cast<std::int32_t>(KeyCode::F12)) return "F12";
if (keyCode == static_cast<std::int32_t>(KeyCode::Tab)) return "Tab";
if (keyCode == static_cast<std::int32_t>(KeyCode::Enter)) return "Enter";
if (keyCode == static_cast<std::int32_t>(KeyCode::Escape)) return "Esc";
if (keyCode == static_cast<std::int32_t>(KeyCode::Space)) return "Space";
if (keyCode == static_cast<std::int32_t>(KeyCode::Delete)) return "Delete";
if (keyCode == static_cast<std::int32_t>(KeyCode::Backspace)) return "Backspace";
if (keyCode == static_cast<std::int32_t>(KeyCode::Up)) return "Up";
if (keyCode == static_cast<std::int32_t>(KeyCode::Down)) return "Down";
if (keyCode == static_cast<std::int32_t>(KeyCode::Left)) return "Left";
if (keyCode == static_cast<std::int32_t>(KeyCode::Right)) return "Right";
if (keyCode == static_cast<std::int32_t>(KeyCode::Home)) return "Home";
if (keyCode == static_cast<std::int32_t>(KeyCode::End)) return "End";
if (keyCode == static_cast<std::int32_t>(KeyCode::PageUp)) return "PageUp";
if (keyCode == static_cast<std::int32_t>(KeyCode::PageDown)) return "PageDown";
if (keyCode == static_cast<std::int32_t>(KeyCode::Minus)) return "-";
if (keyCode == static_cast<std::int32_t>(KeyCode::Equals)) return "=";
if (keyCode == static_cast<std::int32_t>(KeyCode::BracketLeft)) return "[";
if (keyCode == static_cast<std::int32_t>(KeyCode::Semicolon)) return ";";
if (keyCode == static_cast<std::int32_t>(KeyCode::Period)) return ".";
if (keyCode == static_cast<std::int32_t>(KeyCode::Slash)) return "/";
if (keyCode == static_cast<std::int32_t>(KeyCode::Backslash)) return "\\";
if (keyCode == static_cast<std::int32_t>(KeyCode::Backtick)) return "`";
return keyCode == 0
? std::string()
: "Key(" + std::to_string(keyCode) + ")";
}
std::string FormatShortcutChord(const XCEngine::UI::UIShortcutChord& chord) {
std::string result = {};
if (chord.modifiers.control) {
result += "Ctrl+";
}
if (chord.modifiers.shift) {
result += "Shift+";
}
if (chord.modifiers.alt) {
result += "Alt+";
}
if (chord.modifiers.super) {
result += "Super+";
}
result += GetKeyCodeDisplayName(chord.keyCode);
return result;
}
} // namespace
std::string_view GetUIEditorShortcutDispatchStatusName(
UIEditorShortcutDispatchStatus status) {
switch (status) {
case UIEditorShortcutDispatchStatus::NoMatch:
return "NoMatch";
case UIEditorShortcutDispatchStatus::Suppressed:
return "Suppressed";
case UIEditorShortcutDispatchStatus::Dispatched:
return "Dispatched";
case UIEditorShortcutDispatchStatus::Rejected:
return "Rejected";
}
return "Unknown";
}
UIEditorShortcutManager::UIEditorShortcutManager(UIEditorCommandRegistry commandRegistry)
: m_commandDispatcher(std::move(commandRegistry)) {
}
std::uint64_t UIEditorShortcutManager::RegisterBinding(
const XCEngine::UI::UIShortcutBinding& binding) {
return m_shortcutRegistry.RegisterBinding(binding);
}
bool UIEditorShortcutManager::UnregisterBinding(std::uint64_t bindingId) {
return m_shortcutRegistry.UnregisterBinding(bindingId);
}
void UIEditorShortcutManager::ClearBindings() {
m_shortcutRegistry.Clear();
}
UIEditorShortcutManagerValidationResult UIEditorShortcutManager::ValidateConfiguration() const {
const UIEditorCommandRegistryValidationResult commandValidation =
m_commandDispatcher.ValidateConfiguration();
if (!commandValidation.IsValid()) {
return MakeValidationError(
UIEditorShortcutManagerValidationCode::InvalidCommandRegistry,
commandValidation.message);
}
const std::vector<UIShortcutBinding>& bindings = m_shortcutRegistry.GetBindings();
for (std::size_t index = 0; index < bindings.size(); ++index) {
const UIShortcutBinding& binding = bindings[index];
if (binding.commandId.empty()) {
return MakeValidationError(
UIEditorShortcutManagerValidationCode::EmptyBindingCommandId,
"Editor shortcut binding commandId must not be empty.");
}
if (FindUIEditorCommandDescriptor(
m_commandDispatcher.GetCommandRegistry(),
binding.commandId) == nullptr) {
return MakeValidationError(
UIEditorShortcutManagerValidationCode::UnknownCommandId,
"Editor shortcut binding references unknown command '" + binding.commandId + "'.");
}
if (binding.chord.keyCode == 0) {
return MakeValidationError(
UIEditorShortcutManagerValidationCode::EmptyShortcutKey,
"Editor shortcut binding '" + binding.commandId + "' must define a keyCode.");
}
if (binding.scope != UIShortcutScope::Global && binding.ownerId == 0) {
return MakeValidationError(
UIEditorShortcutManagerValidationCode::MissingScopedOwnerId,
"Editor shortcut binding '" + binding.commandId + "' must define ownerId for non-global scope.");
}
for (std::size_t candidateIndex = index + 1u; candidateIndex < bindings.size(); ++candidateIndex) {
const UIShortcutBinding& candidate = bindings[candidateIndex];
if (HaveConflictingChord(binding, candidate)) {
return MakeValidationError(
UIEditorShortcutManagerValidationCode::ConflictingBinding,
"Editor shortcut bindings '" + binding.commandId +
"' and '" + candidate.commandId +
"' conflict on the same chord/scope/owner.");
}
}
}
return {};
}
const XCEngine::UI::UIShortcutBinding* UIEditorShortcutManager::FindPreferredBinding(
std::string_view commandId) const {
const UIShortcutBinding* preferred = nullptr;
for (const UIShortcutBinding& binding : m_shortcutRegistry.GetBindings()) {
if (binding.commandId != commandId) {
continue;
}
if (preferred == nullptr) {
preferred = &binding;
continue;
}
const int currentPriority = ShortcutDisplayPriority(binding.scope);
const int preferredPriority = ShortcutDisplayPriority(preferred->scope);
if (currentPriority < preferredPriority ||
(currentPriority == preferredPriority &&
(binding.ownerId < preferred->ownerId ||
(binding.ownerId == preferred->ownerId &&
binding.bindingId < preferred->bindingId)))) {
preferred = &binding;
}
}
return preferred;
}
std::string UIEditorShortcutManager::GetPreferredShortcutText(
std::string_view commandId) const {
const UIShortcutBinding* binding = FindPreferredBinding(commandId);
return binding != nullptr ? FormatShortcutChord(binding->chord) : std::string();
}
UIEditorShortcutDispatchResult UIEditorShortcutManager::BuildDispatchResult(
UIEditorShortcutDispatchStatus status,
std::string commandId,
std::string commandDisplayName,
std::string message,
const UIShortcutMatch* match) const {
UIEditorShortcutDispatchResult result = {};
result.status = status;
result.matched = match != nullptr && match->matched;
result.commandId = std::move(commandId);
result.commandDisplayName = std::move(commandDisplayName);
result.message = std::move(message);
if (match != nullptr && match->matched) {
result.shortcutScope = match->binding.scope;
result.shortcutOwnerId = match->binding.ownerId;
}
return result;
}
UIEditorShortcutDispatchResult UIEditorShortcutManager::Dispatch(
const XCEngine::UI::UIInputEvent& event,
const XCEngine::UI::UIShortcutContext& shortcutContext,
UIEditorWorkspaceController& controller) const {
const UIEditorShortcutManagerValidationResult validation = ValidateConfiguration();
if (!validation.IsValid()) {
return BuildDispatchResult(
UIEditorShortcutDispatchStatus::Rejected,
{},
{},
"Shortcut manager configuration invalid: " + validation.message);
}
const UIShortcutMatch match = m_shortcutRegistry.Match(event, shortcutContext);
if (!match.matched) {
return BuildDispatchResult(
UIEditorShortcutDispatchStatus::NoMatch,
{},
{},
"No shortcut binding matched the input event.");
}
const UIEditorCommandDescriptor* descriptor =
FindUIEditorCommandDescriptor(
m_commandDispatcher.GetCommandRegistry(),
match.binding.commandId);
if (descriptor == nullptr) {
return BuildDispatchResult(
UIEditorShortcutDispatchStatus::Rejected,
match.binding.commandId,
{},
"Matched shortcut references an unknown editor command.",
&match);
}
if (shortcutContext.textInputActive) {
return BuildDispatchResult(
UIEditorShortcutDispatchStatus::Suppressed,
descriptor->commandId,
descriptor->displayName,
"Shortcut matched but was suppressed by active text input.",
&match);
}
const UIEditorCommandDispatchResult dispatchResult =
m_commandDispatcher.Dispatch(descriptor->commandId, controller);
if (!dispatchResult.commandExecuted) {
return BuildDispatchResult(
UIEditorShortcutDispatchStatus::Rejected,
descriptor->commandId,
descriptor->displayName,
dispatchResult.message,
&match);
}
UIEditorShortcutDispatchResult result = BuildDispatchResult(
UIEditorShortcutDispatchStatus::Dispatched,
descriptor->commandId,
descriptor->displayName,
"Shortcut matched and command dispatched.",
&match);
result.commandExecuted = true;
result.commandResult = dispatchResult.commandResult;
return result;
}
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,385 @@
#include <XCEditor/Foundation/UIEditorTheme.h>
namespace XCEngine::UI::Editor {
namespace {
using ::XCEngine::UI::UIColor;
const UIColor kSurfaceLower(0.09f, 0.09f, 0.09f, 1.0f);
const UIColor kSurfaceBase(0.10f, 0.10f, 0.10f, 1.0f);
const UIColor kSurfaceRaised(0.11f, 0.11f, 0.11f, 1.0f);
const UIColor kSurfacePanel(0.12f, 0.12f, 0.12f, 1.0f);
const UIColor kSurfaceHover(0.14f, 0.14f, 0.14f, 1.0f);
const UIColor kSurfaceActive(0.17f, 0.17f, 0.17f, 1.0f);
const UIColor kSurfaceActiveStrong(0.19f, 0.19f, 0.19f, 1.0f);
const UIColor kBorderSubtle(0.14f, 0.14f, 0.14f, 1.0f);
const UIColor kBorderDefault(0.15f, 0.15f, 0.15f, 1.0f);
const UIColor kBorderFocused(0.19f, 0.19f, 0.19f, 1.0f);
const UIColor kTextPrimary(0.92f, 0.92f, 0.92f, 1.0f);
const UIColor kTextStrong(0.94f, 0.94f, 0.94f, 1.0f);
const UIColor kTextSecondary(0.72f, 0.72f, 0.72f, 1.0f);
const UIColor kTextMuted(0.62f, 0.62f, 0.62f, 1.0f);
const UIColor kTextDisabled(0.46f, 0.46f, 0.46f, 1.0f);
template <typename TValue>
const TValue& GetDefaultValue() {
static const TValue value = {};
return value;
}
Widgets::UIEditorMenuBarPalette BuildMenuBarPalette() {
Widgets::UIEditorMenuBarPalette palette = {};
palette.barColor = kSurfaceBase;
palette.buttonColor = UIColor(0.0f, 0.0f, 0.0f, 0.0f);
palette.buttonHoveredColor = kSurfaceHover;
palette.buttonOpenColor = kSurfaceActive;
palette.borderColor = UIColor(0.0f, 0.0f, 0.0f, 0.0f);
palette.focusedBorderColor = UIColor(0.0f, 0.0f, 0.0f, 0.0f);
palette.openBorderColor = UIColor(0.0f, 0.0f, 0.0f, 0.0f);
palette.textPrimary = kTextStrong;
palette.textMuted = kTextSecondary;
palette.textDisabled = kTextDisabled;
return palette;
}
Widgets::UIEditorMenuPopupPalette BuildMenuPopupPalette() {
Widgets::UIEditorMenuPopupPalette palette = {};
palette.popupColor = kSurfaceBase;
palette.borderColor = kBorderDefault;
palette.itemHoverColor = kSurfaceHover;
palette.itemOpenColor = kSurfaceActive;
palette.separatorColor = kBorderSubtle;
palette.textPrimary = kTextStrong;
palette.textMuted = kTextSecondary;
palette.textDisabled = kTextDisabled;
palette.glyphColor = kTextPrimary;
return palette;
}
Widgets::UIEditorListViewPalette BuildListViewPalette() {
Widgets::UIEditorListViewPalette palette = {};
palette.surfaceColor = kSurfaceBase;
palette.borderColor = kBorderDefault;
palette.focusedBorderColor = kBorderFocused;
palette.rowHoverColor = kSurfaceHover;
palette.rowSelectedColor = kSurfaceActive;
palette.rowSelectedFocusedColor = kSurfaceActiveStrong;
palette.primaryTextColor = kTextStrong;
palette.secondaryTextColor = kTextMuted;
return palette;
}
Widgets::UIEditorTreeViewPalette BuildTreeViewPalette() {
Widgets::UIEditorTreeViewPalette palette = {};
palette.surfaceColor = kSurfaceBase;
palette.borderColor = kSurfaceBase;
palette.focusedBorderColor = kSurfaceBase;
palette.rowHoverColor = UIColor(0.13f, 0.13f, 0.13f, 1.0f);
palette.rowSelectedColor = kSurfaceActive;
palette.rowSelectedFocusedColor = kSurfaceActive;
palette.disclosureColor = kTextMuted;
palette.textColor = kTextPrimary;
palette.scrollViewPalette.surfaceColor = palette.surfaceColor;
palette.scrollViewPalette.borderColor = palette.borderColor;
palette.scrollViewPalette.focusedBorderColor = palette.focusedBorderColor;
palette.scrollViewPalette.scrollbarTrackColor = kSurfaceLower;
palette.scrollViewPalette.scrollbarThumbColor = kSurfaceHover;
palette.scrollViewPalette.scrollbarThumbHoverColor = kSurfaceActive;
palette.scrollViewPalette.scrollbarThumbActiveColor = UIColor(0.20f, 0.20f, 0.20f, 1.0f);
return palette;
}
Widgets::UIEditorTreeViewMetrics BuildTreeViewMetrics() {
Widgets::UIEditorTreeViewMetrics metrics = {};
metrics.rowHeight = 20.0f;
metrics.rowGap = 0.0f;
metrics.horizontalPadding = 6.0f;
metrics.indentWidth = 14.0f;
metrics.disclosureExtent = 18.0f;
metrics.disclosureLabelGap = 2.0f;
metrics.iconExtent = 18.0f;
metrics.iconLabelGap = 2.0f;
metrics.iconInsetY = -1.0f;
metrics.labelInsetY = 0.0f;
metrics.cornerRounding = 0.0f;
metrics.borderThickness = 0.0f;
metrics.focusedBorderThickness = 0.0f;
metrics.scrollViewMetrics.scrollbarWidth = 8.0f;
metrics.scrollViewMetrics.scrollbarInset = 3.0f;
metrics.scrollViewMetrics.minThumbHeight = 28.0f;
metrics.scrollViewMetrics.cornerRounding = metrics.cornerRounding;
metrics.scrollViewMetrics.borderThickness = metrics.borderThickness;
metrics.scrollViewMetrics.focusedBorderThickness = metrics.focusedBorderThickness;
return metrics;
}
Widgets::UIEditorScrollViewPalette BuildScrollViewPalette() {
Widgets::UIEditorScrollViewPalette palette = {};
palette.surfaceColor = kSurfaceBase;
palette.borderColor = kBorderDefault;
palette.focusedBorderColor = kBorderFocused;
palette.scrollbarTrackColor = kSurfaceLower;
palette.scrollbarThumbColor = kSurfaceHover;
palette.scrollbarThumbHoverColor = kSurfaceActive;
palette.scrollbarThumbActiveColor = UIColor(0.20f, 0.20f, 0.20f, 1.0f);
return palette;
}
Widgets::UIEditorTabStripPalette BuildTabStripPalette() {
Widgets::UIEditorTabStripPalette palette = {};
palette.stripBackgroundColor = kSurfaceBase;
palette.headerBackgroundColor = kSurfaceRaised;
palette.contentBackgroundColor = kSurfaceBase;
palette.headerContentSeparatorColor = kBorderDefault;
palette.tabColor = kSurfaceRaised;
palette.tabHoveredColor = kSurfaceHover;
palette.tabSelectedColor = kSurfaceRaised;
palette.tabBorderColor = kBorderDefault;
palette.tabHoveredBorderColor = kBorderFocused;
palette.tabSelectedBorderColor = kBorderDefault;
palette.textPrimary = kTextPrimary;
palette.textSecondary = kTextSecondary;
return palette;
}
Widgets::UIEditorStatusBarPalette BuildStatusBarPalette() {
Widgets::UIEditorStatusBarPalette palette = {};
palette.surfaceColor = kSurfaceBase;
palette.borderColor = kBorderDefault;
palette.focusedBorderColor = kBorderFocused;
palette.segmentColor = kSurfacePanel;
palette.segmentHoveredColor = kSurfaceHover;
palette.segmentActiveColor = kSurfaceActive;
palette.segmentBorderColor = kBorderDefault;
palette.separatorColor = kBorderDefault;
palette.textPrimary = kTextPrimary;
palette.textMuted = kTextMuted;
palette.textAccent = kTextStrong;
return palette;
}
Widgets::UIEditorPanelFramePalette BuildPanelFramePalette() {
Widgets::UIEditorPanelFramePalette palette = {};
palette.surfaceColor = kSurfaceBase;
palette.headerColor = kSurfaceRaised;
palette.footerColor = kSurfaceBase;
palette.borderColor = kBorderDefault;
palette.hoveredBorderColor = kSurfaceActive;
palette.activeBorderColor = kBorderFocused;
palette.focusedBorderColor = kBorderFocused;
palette.textPrimary = kTextPrimary;
palette.textSecondary = kTextSecondary;
palette.textMuted = kTextMuted;
palette.actionButtonColor = kSurfaceRaised;
palette.actionButtonHoveredColor = kSurfaceHover;
palette.actionButtonSelectedColor = kSurfaceActive;
palette.actionButtonBorderColor = kBorderDefault;
palette.actionGlyphColor = UIColor(0.86f, 0.86f, 0.86f, 1.0f);
return palette;
}
Widgets::UIEditorDockHostPalette BuildDockHostPalette() {
Widgets::UIEditorDockHostPalette palette = {};
palette.tabStripPalette = BuildTabStripPalette();
palette.panelFramePalette = BuildPanelFramePalette();
palette.splitterColor = kBorderSubtle;
palette.splitterHoveredColor = UIColor(0.16f, 0.16f, 0.16f, 1.0f);
palette.splitterActiveColor = UIColor(0.18f, 0.18f, 0.18f, 1.0f);
palette.placeholderTitleColor = kTextPrimary;
palette.placeholderTextColor = kTextSecondary;
palette.placeholderMutedColor = kTextMuted;
palette.dropPreviewFillColor = UIColor(0.92f, 0.92f, 0.92f, 0.06f);
palette.dropPreviewBorderColor = UIColor(0.95f, 0.95f, 0.95f, 0.55f);
return palette;
}
Widgets::UIEditorViewportSlotPalette BuildViewportSlotPalette() {
Widgets::UIEditorViewportSlotPalette palette = {};
palette.frameColor = kSurfaceLower;
palette.topBarColor = kSurfaceBase;
palette.surfaceColor = kSurfaceLower;
palette.surfaceHoverOverlayColor = UIColor(0.18f, 0.18f, 0.18f, 0.08f);
palette.surfaceActiveOverlayColor = UIColor(0.22f, 0.22f, 0.22f, 0.08f);
palette.captureOverlayColor = UIColor(0.92f, 0.92f, 0.92f, 0.05f);
palette.borderColor = kBorderDefault;
palette.focusedBorderColor = kBorderFocused;
palette.surfaceBorderColor = kBorderDefault;
palette.surfaceHoveredBorderColor = kSurfaceActive;
palette.surfaceActiveBorderColor = kBorderFocused;
palette.surfaceCapturedBorderColor = kBorderFocused;
palette.toolColor = kSurfacePanel;
palette.toolHoveredColor = kSurfaceHover;
palette.toolSelectedColor = kSurfaceActive;
palette.toolDisabledColor = kSurfaceLower;
palette.toolBorderColor = kBorderDefault;
palette.textPrimary = kTextPrimary;
palette.textSecondary = kTextSecondary;
palette.textMuted = kTextMuted;
palette.imageTint = UIColor(1.0f, 1.0f, 1.0f, 1.0f);
palette.statusBarPalette = BuildStatusBarPalette();
return palette;
}
UIEditorShellComposePalette BuildShellComposePalette() {
UIEditorShellComposePalette palette = {};
palette.surfaceColor = kSurfaceBase;
palette.surfaceBorderColor = kBorderDefault;
palette.menuBarPalette = BuildMenuBarPalette();
palette.toolbarPalette.barColor = kSurfaceBase;
palette.toolbarPalette.groupColor = kSurfaceRaised;
palette.toolbarPalette.groupBorderColor = kBorderDefault;
palette.toolbarPalette.buttonColor = kSurfacePanel;
palette.toolbarPalette.buttonBorderColor = kBorderDefault;
palette.toolbarPalette.iconColor = UIColor(0.88f, 0.88f, 0.88f, 1.0f);
palette.dockHostPalette = BuildDockHostPalette();
palette.viewportPalette = BuildViewportSlotPalette();
palette.statusBarPalette = BuildStatusBarPalette();
return palette;
}
UIEditorShellInteractionPalette BuildShellInteractionPalette() {
UIEditorShellInteractionPalette palette = {};
palette.shellPalette = BuildShellComposePalette();
palette.popupPalette = BuildMenuPopupPalette();
return palette;
}
Widgets::UIEditorPropertyGridPalette BuildPropertyGridPalette() {
Widgets::UIEditorPropertyGridPalette palette = {};
palette.surfaceColor = kSurfaceBase;
palette.borderColor = kBorderDefault;
palette.focusedBorderColor = kBorderFocused;
palette.sectionHeaderColor = kSurfaceRaised;
palette.sectionHeaderHoverColor = kSurfaceHover;
palette.fieldHoverColor = kSurfaceHover;
palette.fieldSelectedColor = kSurfaceActive;
palette.fieldSelectedFocusedColor = kSurfaceActiveStrong;
palette.valueBoxColor = kSurfacePanel;
palette.valueBoxHoverColor = kSurfaceHover;
palette.valueBoxEditingColor = kSurfaceActive;
palette.valueBoxReadOnlyColor = kSurfaceBase;
palette.valueBoxBorderColor = kBorderDefault;
palette.valueBoxEditingBorderColor = kBorderFocused;
palette.disclosureColor = kTextMuted;
palette.sectionTextColor = kTextPrimary;
palette.labelTextColor = kTextSecondary;
palette.valueTextColor = kTextPrimary;
palette.readOnlyValueTextColor = UIColor(0.60f, 0.60f, 0.60f, 1.0f);
palette.editTagColor = UIColor(0.80f, 0.80f, 0.80f, 1.0f);
return palette;
}
#define XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Name, Type) \
const Type& ResolveUIEditor##Name() { \
return GetDefaultValue<Type>(); \
}
} // namespace
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(BoolFieldMetrics, Widgets::UIEditorBoolFieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(BoolFieldPalette, Widgets::UIEditorBoolFieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(NumberFieldMetrics, Widgets::UIEditorNumberFieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(NumberFieldPalette, Widgets::UIEditorNumberFieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TextFieldMetrics, Widgets::UIEditorTextFieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TextFieldPalette, Widgets::UIEditorTextFieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector2FieldMetrics, Widgets::UIEditorVector2FieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector2FieldPalette, Widgets::UIEditorVector2FieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector3FieldMetrics, Widgets::UIEditorVector3FieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector3FieldPalette, Widgets::UIEditorVector3FieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector4FieldMetrics, Widgets::UIEditorVector4FieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector4FieldPalette, Widgets::UIEditorVector4FieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(EnumFieldMetrics, Widgets::UIEditorEnumFieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(EnumFieldPalette, Widgets::UIEditorEnumFieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ColorFieldMetrics, Widgets::UIEditorColorFieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ColorFieldPalette, Widgets::UIEditorColorFieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ObjectFieldMetrics, Widgets::UIEditorObjectFieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ObjectFieldPalette, Widgets::UIEditorObjectFieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(AssetFieldMetrics, Widgets::UIEditorAssetFieldMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(AssetFieldPalette, Widgets::UIEditorAssetFieldPalette)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(MenuPopupMetrics, Widgets::UIEditorMenuPopupMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ListViewMetrics, Widgets::UIEditorListViewMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ScrollViewMetrics, Widgets::UIEditorScrollViewMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TabStripMetrics, Widgets::UIEditorTabStripMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(MenuBarMetrics, Widgets::UIEditorMenuBarMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(StatusBarMetrics, Widgets::UIEditorStatusBarMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(PanelFrameMetrics, Widgets::UIEditorPanelFrameMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(DockHostMetrics, Widgets::UIEditorDockHostMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ViewportSlotMetrics, Widgets::UIEditorViewportSlotMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ShellComposeMetrics, UIEditorShellComposeMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ShellInteractionMetrics, UIEditorShellInteractionMetrics)
XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(PropertyGridMetrics, Widgets::UIEditorPropertyGridMetrics)
const Widgets::UIEditorMenuPopupPalette& ResolveUIEditorMenuPopupPalette() {
static const Widgets::UIEditorMenuPopupPalette palette = BuildMenuPopupPalette();
return palette;
}
const Widgets::UIEditorListViewPalette& ResolveUIEditorListViewPalette() {
static const Widgets::UIEditorListViewPalette palette = BuildListViewPalette();
return palette;
}
const Widgets::UIEditorTreeViewPalette& ResolveUIEditorTreeViewPalette() {
static const Widgets::UIEditorTreeViewPalette palette = BuildTreeViewPalette();
return palette;
}
const Widgets::UIEditorTreeViewMetrics& ResolveUIEditorTreeViewMetrics() {
static const Widgets::UIEditorTreeViewMetrics metrics = BuildTreeViewMetrics();
return metrics;
}
const Widgets::UIEditorScrollViewPalette& ResolveUIEditorScrollViewPalette() {
static const Widgets::UIEditorScrollViewPalette palette = BuildScrollViewPalette();
return palette;
}
const Widgets::UIEditorTabStripPalette& ResolveUIEditorTabStripPalette() {
static const Widgets::UIEditorTabStripPalette palette = BuildTabStripPalette();
return palette;
}
const Widgets::UIEditorMenuBarPalette& ResolveUIEditorMenuBarPalette() {
static const Widgets::UIEditorMenuBarPalette palette = BuildMenuBarPalette();
return palette;
}
const Widgets::UIEditorStatusBarPalette& ResolveUIEditorStatusBarPalette() {
static const Widgets::UIEditorStatusBarPalette palette = BuildStatusBarPalette();
return palette;
}
const Widgets::UIEditorPanelFramePalette& ResolveUIEditorPanelFramePalette() {
static const Widgets::UIEditorPanelFramePalette palette = BuildPanelFramePalette();
return palette;
}
const Widgets::UIEditorDockHostPalette& ResolveUIEditorDockHostPalette() {
static const Widgets::UIEditorDockHostPalette palette = BuildDockHostPalette();
return palette;
}
const Widgets::UIEditorViewportSlotPalette& ResolveUIEditorViewportSlotPalette() {
static const Widgets::UIEditorViewportSlotPalette palette = BuildViewportSlotPalette();
return palette;
}
const UIEditorShellComposePalette& ResolveUIEditorShellComposePalette() {
static const UIEditorShellComposePalette palette = BuildShellComposePalette();
return palette;
}
const UIEditorShellInteractionPalette& ResolveUIEditorShellInteractionPalette() {
static const UIEditorShellInteractionPalette palette = BuildShellInteractionPalette();
return palette;
}
const Widgets::UIEditorPropertyGridPalette& ResolveUIEditorPropertyGridPalette() {
static const Widgets::UIEditorPropertyGridPalette palette = BuildPropertyGridPalette();
return palette;
}
#undef XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS
} // namespace XCEngine::UI::Editor