diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 3c76eebb..5c301a0d 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -178,6 +178,8 @@ set(XCUI_EDITOR_HOST_RENDERING_SOURCES app/Rendering/Native/NativeRenderer.cpp app/Rendering/Native/NativeRendererCapture.cpp app/Rendering/Native/NativeRendererDraw.cpp + app/Rendering/Native/NativeRendererDrawContent.cpp + app/Rendering/Native/NativeRendererDrawGeometry.cpp app/Rendering/Native/NativeRendererLifecycle.cpp app/Rendering/Native/NativeRendererRenderTarget.cpp app/Rendering/Native/NativeRendererRendering.cpp diff --git a/new_editor/app/Rendering/Native/NativeRenderer.h b/new_editor/app/Rendering/Native/NativeRenderer.h index cd230ec2..665f7487 100644 --- a/new_editor/app/Rendering/Native/NativeRenderer.h +++ b/new_editor/app/Rendering/Native/NativeRenderer.h @@ -108,6 +108,41 @@ private: ID2D1SolidColorBrush& solidBrush, const ::XCEngine::UI::UIDrawCommand& command, std::vector& clipStack); + void RenderFilledRectCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command); + void RenderFilledRectLinearGradientCommand( + ID2D1RenderTarget& renderTarget, + const ::XCEngine::UI::UIDrawCommand& command); + void RenderRectOutlineCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command); + void RenderLineCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command); + void RenderFilledTriangleCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command); + void RenderFilledCircleCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command); + void RenderCircleOutlineCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command); + void RenderTextCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command); + void RenderImageCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command); IDWriteTextFormat* GetTextFormat(float fontSize) const; static D2D1_COLOR_F ToD2DColor(const ::XCEngine::UI::UIColor& color); diff --git a/new_editor/app/Rendering/Native/NativeRendererDraw.cpp b/new_editor/app/Rendering/Native/NativeRendererDraw.cpp index f0ff52a8..cb2cea28 100644 --- a/new_editor/app/Rendering/Native/NativeRendererDraw.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererDraw.cpp @@ -1,8 +1,5 @@ #include "NativeRendererSupport.h" -#include -#include - namespace XCEngine::UI::Editor::Host { using namespace NativeRendererSupport; @@ -37,209 +34,37 @@ void NativeRenderer::RenderCommand( const ::XCEngine::UI::UIDrawCommand& command, std::vector& 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, dpiScale); - const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; - if (command.rounding > 0.0f) { - renderTarget.FillRoundedRectangle( - D2D1::RoundedRect(rect, rounding, rounding), - &solidBrush); - } else { - renderTarget.FillRectangle(rect, &solidBrush); - } + case ::XCEngine::UI::UIDrawCommandType::FilledRect: + RenderFilledRectCommand(renderTarget, solidBrush, command); break; - } - case ::XCEngine::UI::UIDrawCommandType::FilledRectLinearGradient: { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; - - const D2D1_GRADIENT_STOP stops[2] = { - D2D1::GradientStop(0.0f, ToD2DColor(command.color)), - D2D1::GradientStop(1.0f, ToD2DColor(command.secondaryColor)) - }; - - Microsoft::WRL::ComPtr stopCollection; - HRESULT hr = renderTarget.CreateGradientStopCollection( - stops, - 2u, - stopCollection.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !stopCollection) { - break; - } - - const D2D1_POINT_2F startPoint = - command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical - ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.top) - : D2D1::Point2F(rect.left, (rect.top + rect.bottom) * 0.5f); - const D2D1_POINT_2F endPoint = - command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical - ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.bottom) - : D2D1::Point2F(rect.right, (rect.top + rect.bottom) * 0.5f); - - Microsoft::WRL::ComPtr gradientBrush; - hr = renderTarget.CreateLinearGradientBrush( - D2D1::LinearGradientBrushProperties(startPoint, endPoint), - stopCollection.Get(), - gradientBrush.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !gradientBrush) { - break; - } - - if (command.rounding > 0.0f) { - renderTarget.FillRoundedRectangle( - D2D1::RoundedRect(rect, rounding, rounding), - gradientBrush.Get()); - } else { - renderTarget.FillRectangle(rect, gradientBrush.Get()); - } + case ::XCEngine::UI::UIDrawCommandType::FilledRectLinearGradient: + RenderFilledRectLinearGradientCommand(renderTarget, command); break; - } - case ::XCEngine::UI::UIDrawCommandType::RectOutline: { - 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, rounding, rounding), - &solidBrush, - thickness); - } else { - renderTarget.DrawRectangle(rect, &solidBrush, thickness); - } + case ::XCEngine::UI::UIDrawCommandType::RectOutline: + RenderRectOutlineCommand(renderTarget, solidBrush, command); break; - } - case ::XCEngine::UI::UIDrawCommandType::Line: { - const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; - const float pixelOffset = ResolveStrokePixelOffset(thickness); - const D2D1_POINT_2F start = ToD2DPoint(command.position, dpiScale, pixelOffset); - const D2D1_POINT_2F end = ToD2DPoint(command.uvMin, dpiScale, pixelOffset); - renderTarget.DrawLine(start, end, &solidBrush, thickness); + case ::XCEngine::UI::UIDrawCommandType::Line: + RenderLineCommand(renderTarget, solidBrush, command); break; - } - case ::XCEngine::UI::UIDrawCommandType::FilledTriangle: { - Microsoft::WRL::ComPtr geometry; - HRESULT hr = m_d2dFactory->CreatePathGeometry(geometry.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !geometry) { - break; - } - - Microsoft::WRL::ComPtr sink; - hr = geometry->Open(sink.ReleaseAndGetAddressOf()); - if (FAILED(hr) || !sink) { - break; - } - - const D2D1_POINT_2F a = ToD2DPoint(command.position, dpiScale); - const D2D1_POINT_2F b = ToD2DPoint(command.uvMin, dpiScale); - const D2D1_POINT_2F c = ToD2DPoint(command.uvMax, dpiScale); - const D2D1_POINT_2F points[2] = { b, c }; - sink->BeginFigure(a, D2D1_FIGURE_BEGIN_FILLED); - sink->AddLines(points, 2u); - sink->EndFigure(D2D1_FIGURE_END_CLOSED); - hr = sink->Close(); - if (FAILED(hr)) { - break; - } - - renderTarget.FillGeometry(geometry.Get(), &solidBrush); + case ::XCEngine::UI::UIDrawCommandType::FilledTriangle: + RenderFilledTriangleCommand(renderTarget, solidBrush, command); break; - } - case ::XCEngine::UI::UIDrawCommandType::FilledCircle: { - const float radius = command.radius * dpiScale; - renderTarget.FillEllipse( - D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), - &solidBrush); + case ::XCEngine::UI::UIDrawCommandType::FilledCircle: + RenderFilledCircleCommand(renderTarget, solidBrush, command); break; - } - case ::XCEngine::UI::UIDrawCommandType::CircleOutline: { - const float radius = command.radius * dpiScale; - const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; - renderTarget.DrawEllipse( - D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), - &solidBrush, - thickness); + case ::XCEngine::UI::UIDrawCommandType::CircleOutline: + RenderCircleOutlineCommand(renderTarget, solidBrush, command); break; - } - case ::XCEngine::UI::UIDrawCommandType::Text: { - if (command.text.empty()) { - break; - } - - const float fontSize = ResolveFontSize(command.fontSize); - const float scaledFontSize = fontSize * dpiScale; - IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize); - if (textFormat == nullptr) { - break; - } - - const std::wstring text = Utf8ToWide(command.text); - if (text.empty()) { - break; - } - - 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( - originX, - originY, - targetSize.width, - originY + lineHeight); - renderTarget.DrawTextW( - text.c_str(), - static_cast(text.size()), - textFormat, - layoutRect, - &solidBrush, - D2D1_DRAW_TEXT_OPTIONS_CLIP, - DWRITE_MEASURING_MODE_GDI_NATURAL); + case ::XCEngine::UI::UIDrawCommandType::Text: + RenderTextCommand(renderTarget, solidBrush, command); break; - } - case ::XCEngine::UI::UIDrawCommandType::Image: { - if (!command.texture.IsValid()) { - break; - } - - Microsoft::WRL::ComPtr bitmap; - if (command.texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) { - if (!ResolveInteropBitmap(command.texture, bitmap) || !bitmap) { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); - break; - } - } else { - auto* texture = reinterpret_cast(command.texture.nativeHandle); - if (texture == nullptr || m_liveTextures.find(texture) == m_liveTextures.end()) { - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); - break; - } - - if (!ResolveTextureBitmap(renderTarget, *texture, bitmap) || !bitmap) { - break; - } - } - - const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - const float sourceWidth = static_cast(command.texture.width); - const float sourceHeight = static_cast(command.texture.height); - const float sourceLeft = sourceWidth * std::clamp(command.uvMin.x, 0.0f, 1.0f); - const float sourceTop = sourceHeight * std::clamp(command.uvMin.y, 0.0f, 1.0f); - const float sourceRight = sourceWidth * std::clamp(command.uvMax.x, 0.0f, 1.0f); - const float sourceBottom = sourceHeight * std::clamp(command.uvMax.y, 0.0f, 1.0f); - renderTarget.DrawBitmap( - bitmap.Get(), - rect, - std::clamp(command.color.a, 0.0f, 1.0f), - D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, - D2D1::RectF(sourceLeft, sourceTop, sourceRight, sourceBottom)); + case ::XCEngine::UI::UIDrawCommandType::Image: + RenderImageCommand(renderTarget, solidBrush, command); break; - } case ::XCEngine::UI::UIDrawCommandType::PushClipRect: { + const float dpiScale = ClampDpiScale(m_dpiScale); const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); renderTarget.PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); clipStack.push_back(rect); diff --git a/new_editor/app/Rendering/Native/NativeRendererDrawContent.cpp b/new_editor/app/Rendering/Native/NativeRendererDrawContent.cpp new file mode 100644 index 00000000..3a4cee54 --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRendererDrawContent.cpp @@ -0,0 +1,94 @@ +#include "NativeRendererSupport.h" + +#include +#include + +namespace XCEngine::UI::Editor::Host { + +using namespace NativeRendererSupport; + +void NativeRenderer::RenderTextCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command) { + if (command.text.empty()) { + return; + } + + const float dpiScale = ClampDpiScale(m_dpiScale); + const float fontSize = ResolveFontSize(command.fontSize); + const float scaledFontSize = fontSize * dpiScale; + IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize); + if (textFormat == nullptr) { + return; + } + + const std::wstring text = Utf8ToWide(command.text); + if (text.empty()) { + return; + } + + 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( + originX, + originY, + targetSize.width, + originY + lineHeight); + renderTarget.DrawTextW( + text.c_str(), + static_cast(text.size()), + textFormat, + layoutRect, + &solidBrush, + D2D1_DRAW_TEXT_OPTIONS_CLIP, + DWRITE_MEASURING_MODE_GDI_NATURAL); +} + +void NativeRenderer::RenderImageCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command) { + if (!command.texture.IsValid()) { + return; + } + + const float dpiScale = ClampDpiScale(m_dpiScale); + Microsoft::WRL::ComPtr bitmap; + if (command.texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) { + if (!ResolveInteropBitmap(command.texture, bitmap) || !bitmap) { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); + return; + } + } else { + auto* texture = reinterpret_cast(command.texture.nativeHandle); + if (texture == nullptr || m_liveTextures.find(texture) == m_liveTextures.end()) { + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); + return; + } + + if (!ResolveTextureBitmap(renderTarget, *texture, bitmap) || !bitmap) { + return; + } + } + + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + const float sourceWidth = static_cast(command.texture.width); + const float sourceHeight = static_cast(command.texture.height); + const float sourceLeft = sourceWidth * std::clamp(command.uvMin.x, 0.0f, 1.0f); + const float sourceTop = sourceHeight * std::clamp(command.uvMin.y, 0.0f, 1.0f); + const float sourceRight = sourceWidth * std::clamp(command.uvMax.x, 0.0f, 1.0f); + const float sourceBottom = sourceHeight * std::clamp(command.uvMax.y, 0.0f, 1.0f); + renderTarget.DrawBitmap( + bitmap.Get(), + rect, + std::clamp(command.color.a, 0.0f, 1.0f), + D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, + D2D1::RectF(sourceLeft, sourceTop, sourceRight, sourceBottom)); +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/Native/NativeRendererDrawGeometry.cpp b/new_editor/app/Rendering/Native/NativeRendererDrawGeometry.cpp new file mode 100644 index 00000000..4acb5bf7 --- /dev/null +++ b/new_editor/app/Rendering/Native/NativeRendererDrawGeometry.cpp @@ -0,0 +1,162 @@ +#include "NativeRendererSupport.h" + +namespace XCEngine::UI::Editor::Host { + +using namespace NativeRendererSupport; + +void NativeRenderer::RenderFilledRectCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command) { + const float dpiScale = ClampDpiScale(m_dpiScale); + 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, rounding, rounding), + &solidBrush); + return; + } + + renderTarget.FillRectangle(rect, &solidBrush); +} + +void NativeRenderer::RenderFilledRectLinearGradientCommand( + ID2D1RenderTarget& renderTarget, + const ::XCEngine::UI::UIDrawCommand& command) { + const float dpiScale = ClampDpiScale(m_dpiScale); + const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); + const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; + + const D2D1_GRADIENT_STOP stops[2] = { + D2D1::GradientStop(0.0f, ToD2DColor(command.color)), + D2D1::GradientStop(1.0f, ToD2DColor(command.secondaryColor)) + }; + + Microsoft::WRL::ComPtr stopCollection; + HRESULT hr = renderTarget.CreateGradientStopCollection( + stops, + 2u, + stopCollection.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !stopCollection) { + return; + } + + const D2D1_POINT_2F startPoint = + command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical + ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.top) + : D2D1::Point2F(rect.left, (rect.top + rect.bottom) * 0.5f); + const D2D1_POINT_2F endPoint = + command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical + ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.bottom) + : D2D1::Point2F(rect.right, (rect.top + rect.bottom) * 0.5f); + + Microsoft::WRL::ComPtr gradientBrush; + hr = renderTarget.CreateLinearGradientBrush( + D2D1::LinearGradientBrushProperties(startPoint, endPoint), + stopCollection.Get(), + gradientBrush.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !gradientBrush) { + return; + } + + if (command.rounding > 0.0f) { + renderTarget.FillRoundedRectangle( + D2D1::RoundedRect(rect, rounding, rounding), + gradientBrush.Get()); + return; + } + + renderTarget.FillRectangle(rect, gradientBrush.Get()); +} + +void NativeRenderer::RenderRectOutlineCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command) { + const float dpiScale = ClampDpiScale(m_dpiScale); + 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, rounding, rounding), + &solidBrush, + thickness); + return; + } + + renderTarget.DrawRectangle(rect, &solidBrush, thickness); +} + +void NativeRenderer::RenderLineCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command) { + const float dpiScale = ClampDpiScale(m_dpiScale); + const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; + const float pixelOffset = ResolveStrokePixelOffset(thickness); + const D2D1_POINT_2F start = ToD2DPoint(command.position, dpiScale, pixelOffset); + const D2D1_POINT_2F end = ToD2DPoint(command.uvMin, dpiScale, pixelOffset); + renderTarget.DrawLine(start, end, &solidBrush, thickness); +} + +void NativeRenderer::RenderFilledTriangleCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command) { + Microsoft::WRL::ComPtr geometry; + HRESULT hr = m_d2dFactory->CreatePathGeometry(geometry.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !geometry) { + return; + } + + Microsoft::WRL::ComPtr sink; + hr = geometry->Open(sink.ReleaseAndGetAddressOf()); + if (FAILED(hr) || !sink) { + return; + } + + const float dpiScale = ClampDpiScale(m_dpiScale); + const D2D1_POINT_2F points[3] = { + ToD2DPoint(command.position, dpiScale), + ToD2DPoint(command.uvMin, dpiScale), + ToD2DPoint(command.uvMax, dpiScale) + }; + + sink->BeginFigure(points[0], D2D1_FIGURE_BEGIN_FILLED); + sink->AddLines(points + 1, 2u); + sink->EndFigure(D2D1_FIGURE_END_CLOSED); + hr = sink->Close(); + if (FAILED(hr)) { + return; + } + + renderTarget.FillGeometry(geometry.Get(), &solidBrush); +} + +void NativeRenderer::RenderFilledCircleCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command) { + const float dpiScale = ClampDpiScale(m_dpiScale); + const float radius = command.radius * dpiScale; + renderTarget.FillEllipse( + D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), + &solidBrush); +} + +void NativeRenderer::RenderCircleOutlineCommand( + ID2D1RenderTarget& renderTarget, + ID2D1SolidColorBrush& solidBrush, + const ::XCEngine::UI::UIDrawCommand& command) { + const float dpiScale = ClampDpiScale(m_dpiScale); + const float radius = command.radius * dpiScale; + const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; + renderTarget.DrawEllipse( + D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), + &solidBrush, + thickness); +} + +} // namespace XCEngine::UI::Editor::Host