Refine XCEditor docking and DPI rendering
This commit is contained in:
@@ -8,9 +8,8 @@ namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
namespace {
|
||||
|
||||
D2D1_RECT_F ToD2DRect(const ::XCEngine::UI::UIRect& rect) {
|
||||
return D2D1::RectF(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
|
||||
}
|
||||
constexpr float kBaseDpi = 96.0f;
|
||||
constexpr float kDefaultFontSize = 16.0f;
|
||||
|
||||
std::string HrToString(const char* operation, HRESULT hr) {
|
||||
char buffer[128] = {};
|
||||
@@ -18,6 +17,27 @@ std::string HrToString(const char* operation, HRESULT hr) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
float ClampDpiScale(float dpiScale) {
|
||||
return dpiScale > 0.0f ? dpiScale : 1.0f;
|
||||
}
|
||||
|
||||
float ResolveFontSize(float fontSize) {
|
||||
return fontSize > 0.0f ? fontSize : kDefaultFontSize;
|
||||
}
|
||||
|
||||
float SnapToPixel(float value, float dpiScale) {
|
||||
const float scale = ClampDpiScale(dpiScale);
|
||||
return std::round(value * scale);
|
||||
}
|
||||
|
||||
D2D1_RECT_F ToD2DRect(const ::XCEngine::UI::UIRect& rect, float dpiScale) {
|
||||
const float left = SnapToPixel(rect.x, dpiScale);
|
||||
const float top = SnapToPixel(rect.y, dpiScale);
|
||||
const float right = SnapToPixel(rect.x + rect.width, dpiScale);
|
||||
const float bottom = SnapToPixel(rect.y + rect.height, dpiScale);
|
||||
return D2D1::RectF(left, top, right, bottom);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool NativeRenderer::Initialize(HWND hwnd) {
|
||||
@@ -64,6 +84,17 @@ void NativeRenderer::Shutdown() {
|
||||
m_hwnd = nullptr;
|
||||
}
|
||||
|
||||
void NativeRenderer::SetDpiScale(float dpiScale) {
|
||||
m_dpiScale = ClampDpiScale(dpiScale);
|
||||
if (m_renderTarget) {
|
||||
m_renderTarget->SetDpi(kBaseDpi, kBaseDpi);
|
||||
}
|
||||
}
|
||||
|
||||
float NativeRenderer::GetDpiScale() const {
|
||||
return m_dpiScale;
|
||||
}
|
||||
|
||||
void NativeRenderer::Resize(UINT width, UINT height) {
|
||||
if (!m_renderTarget || width == 0 || height == 0) {
|
||||
return;
|
||||
@@ -104,6 +135,52 @@ const std::string& NativeRenderer::GetLastRenderError() const {
|
||||
return m_lastRenderError;
|
||||
}
|
||||
|
||||
float NativeRenderer::MeasureTextWidth(
|
||||
const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const {
|
||||
if (!m_dwriteFactory || request.text.empty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const std::wstring text = Utf8ToWide(request.text);
|
||||
if (text.empty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const float dpiScale = ClampDpiScale(m_dpiScale);
|
||||
const float scaledFontSize = ResolveFontSize(request.fontSize) * dpiScale;
|
||||
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
|
||||
if (textFormat == nullptr) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDWriteTextLayout> textLayout;
|
||||
HRESULT hr = m_dwriteFactory->CreateTextLayout(
|
||||
text.c_str(),
|
||||
static_cast<UINT32>(text.size()),
|
||||
textFormat,
|
||||
4096.0f,
|
||||
scaledFontSize * 2.0f,
|
||||
textLayout.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr) || !textLayout) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
DWRITE_TEXT_METRICS textMetrics = {};
|
||||
hr = textLayout->GetMetrics(&textMetrics);
|
||||
if (FAILED(hr)) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
DWRITE_OVERHANG_METRICS overhangMetrics = {};
|
||||
float width = textMetrics.widthIncludingTrailingWhitespace;
|
||||
if (SUCCEEDED(textLayout->GetOverhangMetrics(&overhangMetrics))) {
|
||||
width += (std::max)(overhangMetrics.left, 0.0f);
|
||||
width += (std::max)(overhangMetrics.right, 0.0f);
|
||||
}
|
||||
|
||||
return std::ceil(width) / dpiScale;
|
||||
}
|
||||
|
||||
bool NativeRenderer::CaptureToPng(
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
UINT width,
|
||||
@@ -146,7 +223,9 @@ bool NativeRenderer::CaptureToPng(
|
||||
|
||||
const D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = D2D1::RenderTargetProperties(
|
||||
D2D1_RENDER_TARGET_TYPE_DEFAULT,
|
||||
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED));
|
||||
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
|
||||
kBaseDpi,
|
||||
kBaseDpi);
|
||||
|
||||
Microsoft::WRL::ComPtr<ID2D1RenderTarget> offscreenRenderTarget;
|
||||
hr = m_d2dFactory->CreateWicBitmapRenderTarget(
|
||||
@@ -301,7 +380,11 @@ bool NativeRenderer::CreateDeviceResources() {
|
||||
const UINT width = static_cast<UINT>((std::max)(clientRect.right - clientRect.left, 1L));
|
||||
const UINT height = static_cast<UINT>((std::max)(clientRect.bottom - clientRect.top, 1L));
|
||||
|
||||
const D2D1_RENDER_TARGET_PROPERTIES renderTargetProps = D2D1::RenderTargetProperties();
|
||||
const D2D1_RENDER_TARGET_PROPERTIES renderTargetProps = D2D1::RenderTargetProperties(
|
||||
D2D1_RENDER_TARGET_TYPE_DEFAULT,
|
||||
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
|
||||
kBaseDpi,
|
||||
kBaseDpi);
|
||||
const D2D1_HWND_RENDER_TARGET_PROPERTIES hwndProps = D2D1::HwndRenderTargetProperties(
|
||||
m_hwnd,
|
||||
D2D1::SizeU(width, height));
|
||||
@@ -324,6 +407,7 @@ bool NativeRenderer::CreateDeviceResources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_renderTarget->SetDpi(kBaseDpi, kBaseDpi);
|
||||
m_renderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
m_lastRenderError.clear();
|
||||
return true;
|
||||
@@ -333,6 +417,7 @@ bool NativeRenderer::RenderToTarget(
|
||||
ID2D1RenderTarget& renderTarget,
|
||||
ID2D1SolidColorBrush& solidBrush,
|
||||
const ::XCEngine::UI::UIDrawData& drawData) {
|
||||
renderTarget.SetDpi(kBaseDpi, kBaseDpi);
|
||||
renderTarget.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
renderTarget.BeginDraw();
|
||||
renderTarget.Clear(D2D1::ColorF(0.04f, 0.05f, 0.06f, 1.0f));
|
||||
@@ -358,13 +443,15 @@ void NativeRenderer::RenderCommand(
|
||||
const ::XCEngine::UI::UIDrawCommand& command,
|
||||
std::vector<D2D1_RECT_F>& clipStack) {
|
||||
solidBrush.SetColor(ToD2DColor(command.color));
|
||||
const float dpiScale = ClampDpiScale(m_dpiScale);
|
||||
|
||||
switch (command.type) {
|
||||
case ::XCEngine::UI::UIDrawCommandType::FilledRect: {
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect);
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
|
||||
const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f;
|
||||
if (command.rounding > 0.0f) {
|
||||
renderTarget.FillRoundedRectangle(
|
||||
D2D1::RoundedRect(rect, command.rounding, command.rounding),
|
||||
D2D1::RoundedRect(rect, rounding, rounding),
|
||||
&solidBrush);
|
||||
} else {
|
||||
renderTarget.FillRectangle(rect, &solidBrush);
|
||||
@@ -372,11 +459,12 @@ void NativeRenderer::RenderCommand(
|
||||
break;
|
||||
}
|
||||
case ::XCEngine::UI::UIDrawCommandType::RectOutline: {
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect);
|
||||
const float thickness = command.thickness > 0.0f ? command.thickness : 1.0f;
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
|
||||
const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale;
|
||||
const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f;
|
||||
if (command.rounding > 0.0f) {
|
||||
renderTarget.DrawRoundedRectangle(
|
||||
D2D1::RoundedRect(rect, command.rounding, command.rounding),
|
||||
D2D1::RoundedRect(rect, rounding, rounding),
|
||||
&solidBrush,
|
||||
thickness);
|
||||
} else {
|
||||
@@ -389,8 +477,9 @@ void NativeRenderer::RenderCommand(
|
||||
break;
|
||||
}
|
||||
|
||||
const float fontSize = command.fontSize > 0.0f ? command.fontSize : 16.0f;
|
||||
IDWriteTextFormat* textFormat = GetTextFormat(fontSize);
|
||||
const float fontSize = ResolveFontSize(command.fontSize);
|
||||
const float scaledFontSize = fontSize * dpiScale;
|
||||
IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize);
|
||||
if (textFormat == nullptr) {
|
||||
break;
|
||||
}
|
||||
@@ -401,11 +490,14 @@ void NativeRenderer::RenderCommand(
|
||||
}
|
||||
|
||||
const D2D1_SIZE_F targetSize = renderTarget.GetSize();
|
||||
const float originX = SnapToPixel(command.position.x, dpiScale);
|
||||
const float originY = SnapToPixel(command.position.y, dpiScale);
|
||||
const float lineHeight = std::ceil(scaledFontSize * 1.6f);
|
||||
const D2D1_RECT_F layoutRect = D2D1::RectF(
|
||||
command.position.x,
|
||||
command.position.y,
|
||||
originX,
|
||||
originY,
|
||||
targetSize.width,
|
||||
command.position.y + fontSize * 1.8f);
|
||||
originY + lineHeight);
|
||||
renderTarget.DrawTextW(
|
||||
text.c_str(),
|
||||
static_cast<UINT32>(text.size()),
|
||||
@@ -413,7 +505,7 @@ void NativeRenderer::RenderCommand(
|
||||
layoutRect,
|
||||
&solidBrush,
|
||||
D2D1_DRAW_TEXT_OPTIONS_CLIP,
|
||||
DWRITE_MEASURING_MODE_NATURAL);
|
||||
DWRITE_MEASURING_MODE_GDI_NATURAL);
|
||||
break;
|
||||
}
|
||||
case ::XCEngine::UI::UIDrawCommandType::Image: {
|
||||
@@ -421,12 +513,12 @@ void NativeRenderer::RenderCommand(
|
||||
break;
|
||||
}
|
||||
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect);
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
|
||||
renderTarget.DrawRectangle(rect, &solidBrush, 1.0f);
|
||||
break;
|
||||
}
|
||||
case ::XCEngine::UI::UIDrawCommandType::PushClipRect: {
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect);
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale);
|
||||
renderTarget.PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
|
||||
clipStack.push_back(rect);
|
||||
break;
|
||||
@@ -443,12 +535,13 @@ void NativeRenderer::RenderCommand(
|
||||
}
|
||||
}
|
||||
|
||||
IDWriteTextFormat* NativeRenderer::GetTextFormat(float fontSize) {
|
||||
IDWriteTextFormat* NativeRenderer::GetTextFormat(float fontSize) const {
|
||||
if (!m_dwriteFactory) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int key = static_cast<int>(std::lround(fontSize * 10.0f));
|
||||
const float resolvedFontSize = ResolveFontSize(fontSize);
|
||||
const int key = static_cast<int>(std::lround(resolvedFontSize * 10.0f));
|
||||
const auto found = m_textFormats.find(key);
|
||||
if (found != m_textFormats.end()) {
|
||||
return found->second.Get();
|
||||
@@ -461,7 +554,7 @@ IDWriteTextFormat* NativeRenderer::GetTextFormat(float fontSize) {
|
||||
DWRITE_FONT_WEIGHT_REGULAR,
|
||||
DWRITE_FONT_STYLE_NORMAL,
|
||||
DWRITE_FONT_STRETCH_NORMAL,
|
||||
fontSize,
|
||||
resolvedFontSize,
|
||||
L"",
|
||||
textFormat.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
|
||||
Reference in New Issue
Block a user