Build XCUI splitter foundation and test harness
This commit is contained in:
52
new_editor/include/XCNewEditor/Host/AutoScreenshot.h
Normal file
52
new_editor/include/XCNewEditor/Host/AutoScreenshot.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine::XCUI::Host {
|
||||
|
||||
class NativeRenderer;
|
||||
|
||||
class AutoScreenshotController {
|
||||
public:
|
||||
void Initialize(const std::filesystem::path& captureRoot);
|
||||
void Shutdown();
|
||||
|
||||
void RequestCapture(std::string reason);
|
||||
void CaptureIfRequested(
|
||||
NativeRenderer& renderer,
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
bool framePresented);
|
||||
|
||||
bool HasPendingCapture() const;
|
||||
const std::filesystem::path& GetLatestCapturePath() const;
|
||||
const std::string& GetLastCaptureSummary() const;
|
||||
const std::string& GetLastCaptureError() const;
|
||||
|
||||
private:
|
||||
std::filesystem::path BuildHistoryCapturePath(std::string_view reason) const;
|
||||
|
||||
static std::string BuildTimestampString();
|
||||
static std::string SanitizeReason(std::string_view reason);
|
||||
|
||||
std::filesystem::path m_captureRoot = {};
|
||||
std::filesystem::path m_historyRoot = {};
|
||||
std::filesystem::path m_latestCapturePath = {};
|
||||
std::string m_pendingReason = {};
|
||||
std::string m_lastCaptureSummary = {};
|
||||
std::string m_lastCaptureError = {};
|
||||
std::uint64_t m_captureCount = 0;
|
||||
bool m_capturePending = false;
|
||||
};
|
||||
|
||||
} // namespace XCEngine::XCUI::Host
|
||||
173
new_editor/include/XCNewEditor/Host/InputModifierTracker.h
Normal file
173
new_editor/include/XCNewEditor/Host/InputModifierTracker.h
Normal file
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine::XCUI::Host {
|
||||
|
||||
class InputModifierTracker {
|
||||
public:
|
||||
void Reset() {
|
||||
m_leftShift = false;
|
||||
m_rightShift = false;
|
||||
m_leftControl = false;
|
||||
m_rightControl = false;
|
||||
m_leftAlt = false;
|
||||
m_rightAlt = false;
|
||||
m_leftSuper = false;
|
||||
m_rightSuper = false;
|
||||
}
|
||||
|
||||
void SyncFromSystemState() {
|
||||
m_leftShift = (GetKeyState(VK_LSHIFT) & 0x8000) != 0;
|
||||
m_rightShift = (GetKeyState(VK_RSHIFT) & 0x8000) != 0;
|
||||
m_leftControl = (GetKeyState(VK_LCONTROL) & 0x8000) != 0;
|
||||
m_rightControl = (GetKeyState(VK_RCONTROL) & 0x8000) != 0;
|
||||
m_leftAlt = (GetKeyState(VK_LMENU) & 0x8000) != 0;
|
||||
m_rightAlt = (GetKeyState(VK_RMENU) & 0x8000) != 0;
|
||||
m_leftSuper = (GetKeyState(VK_LWIN) & 0x8000) != 0;
|
||||
m_rightSuper = (GetKeyState(VK_RWIN) & 0x8000) != 0;
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIInputModifiers GetCurrentModifiers() const {
|
||||
return BuildModifiers();
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIInputModifiers BuildPointerModifiers(std::size_t wParam) const {
|
||||
::XCEngine::UI::UIInputModifiers modifiers = BuildModifiers();
|
||||
modifiers.shift = modifiers.shift || (wParam & MK_SHIFT) != 0;
|
||||
modifiers.control = modifiers.control || (wParam & MK_CONTROL) != 0;
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIInputModifiers ApplyKeyMessage(
|
||||
::XCEngine::UI::UIInputEventType type,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
if (type == ::XCEngine::UI::UIInputEventType::KeyDown) {
|
||||
SetModifierState(ResolveModifierKey(wParam, lParam), true);
|
||||
} else if (type == ::XCEngine::UI::UIInputEventType::KeyUp) {
|
||||
SetModifierState(ResolveModifierKey(wParam, lParam), false);
|
||||
}
|
||||
|
||||
return BuildModifiers();
|
||||
}
|
||||
|
||||
private:
|
||||
enum class ModifierKey : std::uint8_t {
|
||||
None = 0,
|
||||
LeftShift,
|
||||
RightShift,
|
||||
LeftControl,
|
||||
RightControl,
|
||||
LeftAlt,
|
||||
RightAlt,
|
||||
LeftSuper,
|
||||
RightSuper
|
||||
};
|
||||
|
||||
static bool IsExtendedKey(LPARAM lParam) {
|
||||
return (static_cast<std::uint32_t>(lParam) & 0x01000000u) != 0u;
|
||||
}
|
||||
|
||||
static std::uint32_t ExtractScanCode(LPARAM lParam) {
|
||||
return (static_cast<std::uint32_t>(lParam) >> 16u) & 0xffu;
|
||||
}
|
||||
|
||||
static ModifierKey ResolveModifierKey(WPARAM wParam, LPARAM lParam) {
|
||||
switch (static_cast<std::uint32_t>(wParam)) {
|
||||
case VK_SHIFT: {
|
||||
const UINT shiftVirtualKey = MapVirtualKeyW(ExtractScanCode(lParam), MAPVK_VSC_TO_VK_EX);
|
||||
return shiftVirtualKey == VK_RSHIFT
|
||||
? ModifierKey::RightShift
|
||||
: ModifierKey::LeftShift;
|
||||
}
|
||||
case VK_LSHIFT:
|
||||
return ModifierKey::LeftShift;
|
||||
case VK_RSHIFT:
|
||||
return ModifierKey::RightShift;
|
||||
case VK_CONTROL:
|
||||
return IsExtendedKey(lParam)
|
||||
? ModifierKey::RightControl
|
||||
: ModifierKey::LeftControl;
|
||||
case VK_LCONTROL:
|
||||
return ModifierKey::LeftControl;
|
||||
case VK_RCONTROL:
|
||||
return ModifierKey::RightControl;
|
||||
case VK_MENU:
|
||||
return IsExtendedKey(lParam)
|
||||
? ModifierKey::RightAlt
|
||||
: ModifierKey::LeftAlt;
|
||||
case VK_LMENU:
|
||||
return ModifierKey::LeftAlt;
|
||||
case VK_RMENU:
|
||||
return ModifierKey::RightAlt;
|
||||
case VK_LWIN:
|
||||
return ModifierKey::LeftSuper;
|
||||
case VK_RWIN:
|
||||
return ModifierKey::RightSuper;
|
||||
default:
|
||||
return ModifierKey::None;
|
||||
}
|
||||
}
|
||||
|
||||
void SetModifierState(ModifierKey key, bool pressed) {
|
||||
switch (key) {
|
||||
case ModifierKey::LeftShift:
|
||||
m_leftShift = pressed;
|
||||
break;
|
||||
case ModifierKey::RightShift:
|
||||
m_rightShift = pressed;
|
||||
break;
|
||||
case ModifierKey::LeftControl:
|
||||
m_leftControl = pressed;
|
||||
break;
|
||||
case ModifierKey::RightControl:
|
||||
m_rightControl = pressed;
|
||||
break;
|
||||
case ModifierKey::LeftAlt:
|
||||
m_leftAlt = pressed;
|
||||
break;
|
||||
case ModifierKey::RightAlt:
|
||||
m_rightAlt = pressed;
|
||||
break;
|
||||
case ModifierKey::LeftSuper:
|
||||
m_leftSuper = pressed;
|
||||
break;
|
||||
case ModifierKey::RightSuper:
|
||||
m_rightSuper = pressed;
|
||||
break;
|
||||
case ModifierKey::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIInputModifiers BuildModifiers() const {
|
||||
::XCEngine::UI::UIInputModifiers modifiers = {};
|
||||
modifiers.shift = m_leftShift || m_rightShift;
|
||||
modifiers.control = m_leftControl || m_rightControl;
|
||||
modifiers.alt = m_leftAlt || m_rightAlt;
|
||||
modifiers.super = m_leftSuper || m_rightSuper;
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
bool m_leftShift = false;
|
||||
bool m_rightShift = false;
|
||||
bool m_leftControl = false;
|
||||
bool m_rightControl = false;
|
||||
bool m_leftAlt = false;
|
||||
bool m_rightAlt = false;
|
||||
bool m_leftSuper = false;
|
||||
bool m_rightSuper = false;
|
||||
};
|
||||
|
||||
} // namespace XCEngine::XCUI::Host
|
||||
65
new_editor/include/XCNewEditor/Host/NativeRenderer.h
Normal file
65
new_editor/include/XCNewEditor/Host/NativeRenderer.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <d2d1.h>
|
||||
#include <dwrite.h>
|
||||
#include <wincodec.h>
|
||||
#include <windows.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::XCUI::Host {
|
||||
|
||||
class NativeRenderer {
|
||||
public:
|
||||
bool Initialize(HWND hwnd);
|
||||
void Shutdown();
|
||||
void Resize(UINT width, UINT height);
|
||||
bool Render(const ::XCEngine::UI::UIDrawData& drawData);
|
||||
bool CaptureToPng(
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
UINT width,
|
||||
UINT height,
|
||||
const std::filesystem::path& outputPath,
|
||||
std::string& outError);
|
||||
|
||||
private:
|
||||
bool EnsureRenderTarget();
|
||||
bool EnsureWicFactory(std::string& outError);
|
||||
void DiscardRenderTarget();
|
||||
bool CreateDeviceResources();
|
||||
bool RenderToTarget(
|
||||
ID2D1RenderTarget& renderTarget,
|
||||
ID2D1SolidColorBrush& solidBrush,
|
||||
const ::XCEngine::UI::UIDrawData& drawData);
|
||||
void RenderCommand(
|
||||
ID2D1RenderTarget& renderTarget,
|
||||
ID2D1SolidColorBrush& solidBrush,
|
||||
const ::XCEngine::UI::UIDrawCommand& command,
|
||||
std::vector<D2D1_RECT_F>& clipStack);
|
||||
|
||||
IDWriteTextFormat* GetTextFormat(float fontSize);
|
||||
static D2D1_COLOR_F ToD2DColor(const ::XCEngine::UI::UIColor& color);
|
||||
static std::wstring Utf8ToWide(std::string_view text);
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
Microsoft::WRL::ComPtr<ID2D1Factory> m_d2dFactory;
|
||||
Microsoft::WRL::ComPtr<IDWriteFactory> m_dwriteFactory;
|
||||
Microsoft::WRL::ComPtr<IWICImagingFactory> m_wicFactory;
|
||||
Microsoft::WRL::ComPtr<ID2D1HwndRenderTarget> m_renderTarget;
|
||||
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_solidBrush;
|
||||
std::unordered_map<int, Microsoft::WRL::ComPtr<IDWriteTextFormat>> m_textFormats;
|
||||
bool m_wicComInitialized = false;
|
||||
};
|
||||
|
||||
} // namespace XCEngine::XCUI::Host
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Style/Theme.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Widgets {
|
||||
|
||||
enum class UIEditorCollectionPrimitiveKind : std::uint8_t {
|
||||
None = 0,
|
||||
ScrollView,
|
||||
TreeView,
|
||||
TreeItem,
|
||||
ListView,
|
||||
ListItem,
|
||||
PropertySection,
|
||||
FieldRow
|
||||
};
|
||||
|
||||
UIEditorCollectionPrimitiveKind ClassifyUIEditorCollectionPrimitive(std::string_view tagName);
|
||||
bool IsUIEditorCollectionPrimitiveContainer(UIEditorCollectionPrimitiveKind kind);
|
||||
bool UsesUIEditorCollectionPrimitiveColumnLayout(UIEditorCollectionPrimitiveKind kind);
|
||||
bool IsUIEditorCollectionPrimitiveHoverable(UIEditorCollectionPrimitiveKind kind);
|
||||
bool DoesUIEditorCollectionPrimitiveClipChildren(UIEditorCollectionPrimitiveKind kind);
|
||||
float ResolveUIEditorCollectionPrimitivePadding(
|
||||
UIEditorCollectionPrimitiveKind kind,
|
||||
const Style::UITheme& theme);
|
||||
float ResolveUIEditorCollectionPrimitiveDefaultHeight(
|
||||
UIEditorCollectionPrimitiveKind kind,
|
||||
const Style::UITheme& theme);
|
||||
float ResolveUIEditorCollectionPrimitiveIndent(
|
||||
UIEditorCollectionPrimitiveKind kind,
|
||||
const Style::UITheme& theme,
|
||||
float indentLevel);
|
||||
|
||||
} // namespace Widgets
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
126
new_editor/include/XCNewEditor/Widgets/UIEditorPanelChrome.h
Normal file
126
new_editor/include/XCNewEditor/Widgets/UIEditorPanelChrome.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Widgets {
|
||||
|
||||
struct UIEditorPanelChromeState {
|
||||
bool active = false;
|
||||
bool hovered = false;
|
||||
};
|
||||
|
||||
struct UIEditorPanelChromeText {
|
||||
std::string_view title = {};
|
||||
std::string_view subtitle = {};
|
||||
std::string_view footer = {};
|
||||
};
|
||||
|
||||
struct UIEditorPanelChromeMetrics {
|
||||
float cornerRounding = 18.0f;
|
||||
float headerHeight = 42.0f;
|
||||
float titleInsetX = 16.0f;
|
||||
float titleInsetY = 12.0f;
|
||||
float subtitleInsetY = 28.0f;
|
||||
float footerInsetX = 16.0f;
|
||||
float footerInsetBottom = 18.0f;
|
||||
float activeBorderThickness = 2.0f;
|
||||
float inactiveBorderThickness = 1.0f;
|
||||
};
|
||||
|
||||
struct UIEditorPanelChromePalette {
|
||||
UIColor surfaceColor = UIColor(9.0f / 255.0f, 13.0f / 255.0f, 18.0f / 255.0f, 212.0f / 255.0f);
|
||||
UIColor borderColor = UIColor(53.0f / 255.0f, 72.0f / 255.0f, 96.0f / 255.0f, 1.0f);
|
||||
UIColor accentColor = UIColor(84.0f / 255.0f, 176.0f / 255.0f, 244.0f / 255.0f, 1.0f);
|
||||
UIColor hoveredAccentColor = UIColor(1.0f, 206.0f / 255.0f, 112.0f / 255.0f, 1.0f);
|
||||
UIColor headerColor = UIColor(13.0f / 255.0f, 20.0f / 255.0f, 28.0f / 255.0f, 242.0f / 255.0f);
|
||||
UIColor textPrimary = UIColor(232.0f / 255.0f, 238.0f / 255.0f, 246.0f / 255.0f, 1.0f);
|
||||
UIColor textSecondary = UIColor(150.0f / 255.0f, 164.0f / 255.0f, 184.0f / 255.0f, 1.0f);
|
||||
UIColor textMuted = UIColor(108.0f / 255.0f, 123.0f / 255.0f, 145.0f / 255.0f, 1.0f);
|
||||
};
|
||||
|
||||
inline UIRect BuildUIEditorPanelChromeHeaderRect(
|
||||
const UIRect& panelRect,
|
||||
const UIEditorPanelChromeMetrics& metrics = {}) {
|
||||
return UIRect(
|
||||
panelRect.x,
|
||||
panelRect.y,
|
||||
panelRect.width,
|
||||
metrics.headerHeight);
|
||||
}
|
||||
|
||||
inline UIColor ResolveUIEditorPanelChromeBorderColor(
|
||||
const UIEditorPanelChromeState& state,
|
||||
const UIEditorPanelChromePalette& palette = {}) {
|
||||
if (state.active) {
|
||||
return palette.accentColor;
|
||||
}
|
||||
|
||||
if (state.hovered) {
|
||||
return palette.hoveredAccentColor;
|
||||
}
|
||||
|
||||
return palette.borderColor;
|
||||
}
|
||||
|
||||
inline float ResolveUIEditorPanelChromeBorderThickness(
|
||||
const UIEditorPanelChromeState& state,
|
||||
const UIEditorPanelChromeMetrics& metrics = {}) {
|
||||
return state.active
|
||||
? metrics.activeBorderThickness
|
||||
: metrics.inactiveBorderThickness;
|
||||
}
|
||||
|
||||
inline void AppendUIEditorPanelChromeBackground(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& panelRect,
|
||||
const UIEditorPanelChromeState& state,
|
||||
const UIEditorPanelChromePalette& palette = {},
|
||||
const UIEditorPanelChromeMetrics& metrics = {}) {
|
||||
drawList.AddFilledRect(panelRect, palette.surfaceColor, metrics.cornerRounding);
|
||||
drawList.AddRectOutline(
|
||||
panelRect,
|
||||
ResolveUIEditorPanelChromeBorderColor(state, palette),
|
||||
ResolveUIEditorPanelChromeBorderThickness(state, metrics),
|
||||
metrics.cornerRounding);
|
||||
drawList.AddFilledRect(
|
||||
BuildUIEditorPanelChromeHeaderRect(panelRect, metrics),
|
||||
palette.headerColor,
|
||||
metrics.cornerRounding);
|
||||
}
|
||||
|
||||
inline void AppendUIEditorPanelChromeForeground(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& panelRect,
|
||||
const UIEditorPanelChromeText& text,
|
||||
const UIEditorPanelChromePalette& palette = {},
|
||||
const UIEditorPanelChromeMetrics& metrics = {}) {
|
||||
if (!text.title.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(panelRect.x + metrics.titleInsetX, panelRect.y + metrics.titleInsetY),
|
||||
std::string(text.title),
|
||||
palette.textPrimary);
|
||||
}
|
||||
|
||||
if (!text.subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(panelRect.x + metrics.titleInsetX, panelRect.y + metrics.subtitleInsetY),
|
||||
std::string(text.subtitle),
|
||||
palette.textSecondary);
|
||||
}
|
||||
|
||||
if (!text.footer.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(panelRect.x + metrics.footerInsetX, panelRect.y + panelRect.height - metrics.footerInsetBottom),
|
||||
std::string(text.footer),
|
||||
palette.textMuted);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Widgets
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
Reference in New Issue
Block a user