aboutsummaryrefslogtreecommitdiff
path: root/src/win/graphics
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-03-24 19:14:19 +0800
committercrupest <crupest@outlook.com>2021-03-24 19:14:19 +0800
commit7f15a1ff9a2007e119798053083a0a87d042990a (patch)
treecb35c01a7eaee867376d959b96c9bbd15df939e5 /src/win/graphics
parent74956951ee663012df0c3fe4ebe29799cb2f7732 (diff)
parent7703063a5816b089483e78ccd74bb9902ccfbea8 (diff)
downloadcru-7f15a1ff9a2007e119798053083a0a87d042990a.tar.gz
cru-7f15a1ff9a2007e119798053083a0a87d042990a.tar.bz2
cru-7f15a1ff9a2007e119798053083a0a87d042990a.zip
Merge branch 'master' of https://github.com/crupest/CruUI
Diffstat (limited to 'src/win/graphics')
-rw-r--r--src/win/graphics/CMakeLists.txt1
-rw-r--r--src/win/graphics/direct/Brush.cpp17
-rw-r--r--src/win/graphics/direct/CMakeLists.txt29
-rw-r--r--src/win/graphics/direct/Factory.cpp107
-rw-r--r--src/win/graphics/direct/Font.cpp31
-rw-r--r--src/win/graphics/direct/Geometry.cpp62
-rw-r--r--src/win/graphics/direct/Painter.cpp112
-rw-r--r--src/win/graphics/direct/Resource.cpp12
-rw-r--r--src/win/graphics/direct/TextLayout.cpp127
-rw-r--r--src/win/graphics/direct/WindowPainter.cpp20
-rw-r--r--src/win/graphics/direct/WindowRenderTarget.cpp81
11 files changed, 599 insertions, 0 deletions
diff --git a/src/win/graphics/CMakeLists.txt b/src/win/graphics/CMakeLists.txt
new file mode 100644
index 00000000..c90537ac
--- /dev/null
+++ b/src/win/graphics/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(direct)
diff --git a/src/win/graphics/direct/Brush.cpp b/src/win/graphics/direct/Brush.cpp
new file mode 100644
index 00000000..b7842b97
--- /dev/null
+++ b/src/win/graphics/direct/Brush.cpp
@@ -0,0 +1,17 @@
+#include "cru/win/graphics/direct/Brush.hpp"
+
+#include "cru/win/graphics/direct/ConvertUtil.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
+
+namespace cru::platform::graphics::win::direct {
+D2DSolidColorBrush::D2DSolidColorBrush(DirectGraphFactory* factory)
+ : DirectGraphResource(factory) {
+ ThrowIfFailed(factory->GetDefaultD2D1DeviceContext()->CreateSolidColorBrush(
+ Convert(color_), &brush_));
+}
+
+void D2DSolidColorBrush::SetColor(const Color& color) {
+ brush_->SetColor(Convert(color));
+}
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/CMakeLists.txt b/src/win/graphics/direct/CMakeLists.txt
new file mode 100644
index 00000000..d4c96a65
--- /dev/null
+++ b/src/win/graphics/direct/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/win/graphics/direct)
+
+add_library(cru_win_graphics_direct STATIC
+ Brush.cpp
+ Font.cpp
+ Geometry.cpp
+ Factory.cpp
+ Painter.cpp
+ Resource.cpp
+ TextLayout.cpp
+ WindowPainter.cpp
+ WindowRenderTarget.cpp
+)
+target_sources(cru_win_graphics_direct PUBLIC
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Brush.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/ComResource.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/ConvertUtil.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Exception.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Font.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Geometry.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Factory.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Painter.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/Resource.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/TextLayout.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/WindowPainter.hpp
+ ${CRU_WIN_GRAPHICS_DIRECT_INCLUDE_DIR}/WindowRenderTarget.hpp
+)
+target_link_libraries(cru_win_graphics_direct PUBLIC D3D11 D2d1 DWrite)
+target_link_libraries(cru_win_graphics_direct PUBLIC cru_win_base cru_platform_graphics)
diff --git a/src/win/graphics/direct/Factory.cpp b/src/win/graphics/direct/Factory.cpp
new file mode 100644
index 00000000..6694801f
--- /dev/null
+++ b/src/win/graphics/direct/Factory.cpp
@@ -0,0 +1,107 @@
+#include "cru/win/graphics/direct/Factory.hpp"
+
+#include "cru/common/Logger.hpp"
+#include "cru/win/graphics/direct/Brush.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Font.hpp"
+#include "cru/win/graphics/direct/Geometry.hpp"
+#include "cru/win/graphics/direct/TextLayout.hpp"
+
+#include <cstdlib>
+#include <utility>
+
+namespace cru::platform::graphics::win::direct {
+namespace {
+void InitializeCom() {
+ const auto hresult = ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+ if (FAILED(hresult)) {
+ throw HResultError(hresult, "Failed to call CoInitializeEx.");
+ }
+ if (hresult == S_FALSE) {
+ log::Debug(
+ u"Try to call CoInitializeEx, but it seems COM is already "
+ u"initialized.");
+ }
+}
+
+void UninitializeCom() { ::CoUninitialize(); }
+} // namespace
+
+DirectGraphFactory::DirectGraphFactory() {
+ // TODO! Detect repeated creation. Because I don't think we can create two d2d
+ // and dwrite factory so we need to prevent the "probably dangerous" behavior.
+
+ InitializeCom();
+
+ UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+
+#ifdef CRU_DEBUG
+ creation_flags |= D3D11_CREATE_DEVICE_DEBUG;
+#endif
+
+ const D3D_FEATURE_LEVEL feature_levels[] = {
+ D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2,
+ D3D_FEATURE_LEVEL_9_1};
+
+ Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_device_context;
+
+ ThrowIfFailed(D3D11CreateDevice(
+ nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, creation_flags,
+ feature_levels, ARRAYSIZE(feature_levels), D3D11_SDK_VERSION,
+ &d3d11_device_, nullptr, &d3d11_device_context));
+
+ Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
+ ThrowIfFailed(d3d11_device_->QueryInterface(dxgi_device.GetAddressOf()));
+
+ ThrowIfFailed(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
+ IID_PPV_ARGS(&d2d1_factory_)));
+
+ ThrowIfFailed(d2d1_factory_->CreateDevice(dxgi_device.Get(), &d2d1_device_));
+
+ d2d1_device_context_ = CreateD2D1DeviceContext();
+
+ // Identify the physical adapter (GPU or card) this device is runs on.
+ Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
+ ThrowIfFailed(dxgi_device->GetAdapter(&dxgi_adapter));
+
+ // Get the factory object that created the DXGI device.
+ ThrowIfFailed(dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory_)));
+
+ ThrowIfFailed(DWriteCreateFactory(
+ DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
+ reinterpret_cast<IUnknown**>(dwrite_factory_.GetAddressOf())));
+
+ ThrowIfFailed(dwrite_factory_->GetSystemFontCollection(
+ &dwrite_system_font_collection_));
+}
+
+DirectGraphFactory::~DirectGraphFactory() { UninitializeCom(); }
+
+Microsoft::WRL::ComPtr<ID2D1DeviceContext>
+DirectGraphFactory::CreateD2D1DeviceContext() {
+ Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2d1_device_context;
+ ThrowIfFailed(d2d1_device_->CreateDeviceContext(
+ D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &d2d1_device_context));
+ return d2d1_device_context;
+}
+
+std::unique_ptr<ISolidColorBrush> DirectGraphFactory::CreateSolidColorBrush() {
+ return std::make_unique<D2DSolidColorBrush>(this);
+}
+
+std::unique_ptr<IGeometryBuilder> DirectGraphFactory::CreateGeometryBuilder() {
+ return std::make_unique<D2DGeometryBuilder>(this);
+}
+
+std::unique_ptr<IFont> DirectGraphFactory::CreateFont(
+ std::u16string font_family, float font_size) {
+ return std::make_unique<DWriteFont>(this, std::move(font_family), font_size);
+}
+
+std::unique_ptr<ITextLayout> DirectGraphFactory::CreateTextLayout(
+ std::shared_ptr<IFont> font, std::u16string text) {
+ return std::make_unique<DWriteTextLayout>(this, std::move(font),
+ std::move(text));
+}
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/Font.cpp b/src/win/graphics/direct/Font.cpp
new file mode 100644
index 00000000..1d6a5c88
--- /dev/null
+++ b/src/win/graphics/direct/Font.cpp
@@ -0,0 +1,31 @@
+#include "cru/win/graphics/direct/Font.hpp"
+
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
+
+#include <array>
+#include <utility>
+
+namespace cru::platform::graphics::win::direct {
+DWriteFont::DWriteFont(DirectGraphFactory* factory, std::u16string font_family,
+ float font_size)
+ : DirectGraphResource(factory), font_family_(std::move(font_family)) {
+ // Get locale
+ std::array<wchar_t, LOCALE_NAME_MAX_LENGTH> buffer;
+ if (!::GetUserDefaultLocaleName(buffer.data(),
+ static_cast<int>(buffer.size())))
+ throw platform::win::Win32Error(
+ ::GetLastError(), "Failed to get locale when create dwrite font.");
+
+ ThrowIfFailed(factory->GetDWriteFactory()->CreateTextFormat(
+ reinterpret_cast<const wchar_t*>(font_family_.c_str()), nullptr,
+ DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
+ DWRITE_FONT_STRETCH_NORMAL, font_size, buffer.data(), &text_format_));
+
+ ThrowIfFailed(text_format_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING));
+ ThrowIfFailed(
+ text_format_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR));
+}
+
+float DWriteFont::GetFontSize() { return text_format_->GetFontSize(); }
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/Geometry.cpp b/src/win/graphics/direct/Geometry.cpp
new file mode 100644
index 00000000..8aa961b2
--- /dev/null
+++ b/src/win/graphics/direct/Geometry.cpp
@@ -0,0 +1,62 @@
+#include "cru/win/graphics/direct/Geometry.hpp"
+
+#include "cru/win/graphics/direct/ConvertUtil.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
+
+namespace cru::platform::graphics::win::direct {
+D2DGeometryBuilder::D2DGeometryBuilder(DirectGraphFactory* factory)
+ : DirectGraphResource(factory) {
+ ThrowIfFailed(factory->GetD2D1Factory()->CreatePathGeometry(&geometry_));
+ ThrowIfFailed(geometry_->Open(&geometry_sink_));
+}
+
+void D2DGeometryBuilder::CheckValidation() {
+ if (!IsValid())
+ throw ReuseException("The geometry builder is already disposed.");
+}
+
+void D2DGeometryBuilder::BeginFigure(const Point& point) {
+ CheckValidation();
+ geometry_sink_->BeginFigure(Convert(point), D2D1_FIGURE_BEGIN_FILLED);
+}
+
+void D2DGeometryBuilder::LineTo(const Point& point) {
+ CheckValidation();
+ geometry_sink_->AddLine(Convert(point));
+}
+
+void D2DGeometryBuilder::QuadraticBezierTo(const Point& control_point,
+ const Point& end_point) {
+ CheckValidation();
+ geometry_sink_->AddQuadraticBezier(
+ D2D1::QuadraticBezierSegment(Convert(control_point), Convert(end_point)));
+}
+
+void D2DGeometryBuilder::CloseFigure(bool close) {
+ CheckValidation();
+ geometry_sink_->EndFigure(close ? D2D1_FIGURE_END_CLOSED
+ : D2D1_FIGURE_END_OPEN);
+}
+
+std::unique_ptr<IGeometry> D2DGeometryBuilder::Build() {
+ CheckValidation();
+ ThrowIfFailed(geometry_sink_->Close());
+ geometry_sink_ = nullptr;
+ auto geometry =
+ std::make_unique<D2DGeometry>(GetDirectFactory(), std::move(geometry_));
+ geometry_ = nullptr;
+ return geometry;
+}
+
+D2DGeometry::D2DGeometry(DirectGraphFactory* factory,
+ Microsoft::WRL::ComPtr<ID2D1PathGeometry> geometry)
+ : DirectGraphResource(factory), geometry_(std::move(geometry)) {}
+
+bool D2DGeometry::FillContains(const Point& point) {
+ BOOL result;
+ ThrowIfFailed(geometry_->FillContainsPoint(
+ Convert(point), D2D1::Matrix3x2F::Identity(), &result));
+ return result != 0;
+}
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/Painter.cpp b/src/win/graphics/direct/Painter.cpp
new file mode 100644
index 00000000..d6999cfa
--- /dev/null
+++ b/src/win/graphics/direct/Painter.cpp
@@ -0,0 +1,112 @@
+#include "cru/win/graphics/direct/Painter.hpp"
+
+#include "cru/platform/Check.hpp"
+#include "cru/win/graphics/direct/Brush.hpp"
+#include "cru/win/graphics/direct/ConvertUtil.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Geometry.hpp"
+#include "cru/win/graphics/direct/TextLayout.hpp"
+
+#include <type_traits>
+
+namespace cru::platform::graphics::win::direct {
+D2DPainter::D2DPainter(ID2D1RenderTarget* render_target) {
+ Expects(render_target);
+ render_target_ = render_target;
+}
+
+platform::Matrix D2DPainter::GetTransform() {
+ CheckValidation();
+ D2D1_MATRIX_3X2_F m;
+ render_target_->GetTransform(&m);
+ return Convert(m);
+}
+
+void D2DPainter::SetTransform(const platform::Matrix& matrix) {
+ CheckValidation();
+ render_target_->SetTransform(Convert(matrix));
+}
+
+void D2DPainter::Clear(const Color& color) {
+ CheckValidation();
+ render_target_->Clear(Convert(color));
+}
+
+void D2DPainter::DrawLine(const Point& start, const Point& end, IBrush* brush,
+ float width) {
+ CheckValidation();
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ render_target_->DrawLine(Convert(start), Convert(end),
+ b->GetD2DBrushInterface(), width);
+}
+
+void D2DPainter::StrokeRectangle(const Rect& rectangle, IBrush* brush,
+ float width) {
+ CheckValidation();
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ render_target_->DrawRectangle(Convert(rectangle), b->GetD2DBrushInterface(),
+ width);
+}
+
+void D2DPainter::FillRectangle(const Rect& rectangle, IBrush* brush) {
+ CheckValidation();
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ render_target_->FillRectangle(Convert(rectangle), b->GetD2DBrushInterface());
+}
+
+void D2DPainter::StrokeGeometry(IGeometry* geometry, IBrush* brush,
+ float width) {
+ CheckValidation();
+ const auto g = CheckPlatform<D2DGeometry>(geometry, GetPlatformId());
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ render_target_->DrawGeometry(g->GetComInterface(), b->GetD2DBrushInterface(),
+ width);
+}
+
+void D2DPainter::FillGeometry(IGeometry* geometry, IBrush* brush) {
+ CheckValidation();
+ const auto g = CheckPlatform<D2DGeometry>(geometry, GetPlatformId());
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ render_target_->FillGeometry(g->GetComInterface(), b->GetD2DBrushInterface());
+}
+
+void D2DPainter::DrawText(const Point& offset, ITextLayout* text_layout,
+ IBrush* brush) {
+ CheckValidation();
+ const auto t = CheckPlatform<DWriteTextLayout>(text_layout, GetPlatformId());
+ const auto b = CheckPlatform<ID2DBrush>(brush, GetPlatformId());
+ render_target_->DrawTextLayout(Convert(offset), t->GetComInterface(),
+ b->GetD2DBrushInterface());
+}
+
+void D2DPainter::PushLayer(const Rect& bounds) {
+ CheckValidation();
+
+ Microsoft::WRL::ComPtr<ID2D1Layer> layer;
+ ThrowIfFailed(render_target_->CreateLayer(&layer));
+
+ render_target_->PushLayer(D2D1::LayerParameters(Convert(bounds)),
+ layer.Get());
+
+ layers_.push_back(std::move(layer));
+}
+
+void D2DPainter::PopLayer() {
+ render_target_->PopLayer();
+ layers_.pop_back();
+}
+
+void D2DPainter::EndDraw() {
+ if (is_drawing_) {
+ is_drawing_ = false;
+ DoEndDraw();
+ }
+}
+
+void D2DPainter::CheckValidation() {
+ if (!is_drawing_) {
+ throw cru::platform::ReuseException(
+ "Can't do that on painter after end drawing.");
+ }
+}
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/Resource.cpp b/src/win/graphics/direct/Resource.cpp
new file mode 100644
index 00000000..2b4a0772
--- /dev/null
+++ b/src/win/graphics/direct/Resource.cpp
@@ -0,0 +1,12 @@
+#include "cru/win/graphics/direct/Resource.hpp"
+
+#include "cru/win/graphics/direct/Factory.hpp"
+
+namespace cru::platform::graphics::win::direct {
+DirectGraphResource::DirectGraphResource(DirectGraphFactory* factory)
+ : factory_(factory) {
+ Expects(factory);
+}
+
+IGraphFactory* DirectGraphResource::GetGraphFactory() { return factory_; }
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/TextLayout.cpp b/src/win/graphics/direct/TextLayout.cpp
new file mode 100644
index 00000000..0b3c68ca
--- /dev/null
+++ b/src/win/graphics/direct/TextLayout.cpp
@@ -0,0 +1,127 @@
+#include "cru/win/graphics/direct/TextLayout.hpp"
+
+#include "cru/common/Logger.hpp"
+#include "cru/platform/Check.hpp"
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
+#include "cru/win/graphics/direct/Font.hpp"
+
+#include <utility>
+
+namespace cru::platform::graphics::win::direct {
+DWriteTextLayout::DWriteTextLayout(DirectGraphFactory* factory,
+ std::shared_ptr<IFont> font,
+ std::u16string text)
+ : DirectGraphResource(factory), text_(std::move(text)) {
+ Expects(font);
+ font_ = CheckPlatform<DWriteFont>(font, GetPlatformId());
+
+ ThrowIfFailed(factory->GetDWriteFactory()->CreateTextLayout(
+ reinterpret_cast<const wchar_t*>(text_.c_str()),
+ static_cast<UINT32>(text_.size()), font_->GetComInterface(), max_width_,
+ max_height_, &text_layout_));
+}
+
+DWriteTextLayout::~DWriteTextLayout() = default;
+
+std::u16string DWriteTextLayout::GetText() { return text_; }
+
+std::u16string_view DWriteTextLayout::GetTextView() { return text_; }
+
+void DWriteTextLayout::SetText(std::u16string new_text) {
+ text_.swap(new_text);
+ ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout(
+ reinterpret_cast<const wchar_t*>(text_.c_str()),
+ static_cast<UINT32>(text_.size()), font_->GetComInterface(), max_width_,
+ max_height_, &text_layout_));
+}
+
+std::shared_ptr<IFont> DWriteTextLayout::GetFont() {
+ return std::dynamic_pointer_cast<IFont>(font_);
+}
+
+void DWriteTextLayout::SetFont(std::shared_ptr<IFont> font) {
+ font_ = CheckPlatform<DWriteFont>(font, GetPlatformId());
+ ThrowIfFailed(GetDirectFactory()->GetDWriteFactory()->CreateTextLayout(
+ reinterpret_cast<const wchar_t*>(text_.c_str()),
+ static_cast<UINT32>(text_.size()), font_->GetComInterface(), max_width_,
+ max_height_, &text_layout_));
+}
+
+void DWriteTextLayout::SetMaxWidth(float max_width) {
+ max_width_ = max_width;
+ ThrowIfFailed(text_layout_->SetMaxWidth(max_width_));
+}
+
+void DWriteTextLayout::SetMaxHeight(float max_height) {
+ max_height_ = max_height;
+ ThrowIfFailed(text_layout_->SetMaxHeight(max_height_));
+}
+
+Rect DWriteTextLayout::GetTextBounds(bool includingTrailingSpace) {
+ DWRITE_TEXT_METRICS metrics;
+ ThrowIfFailed(text_layout_->GetMetrics(&metrics));
+ return Rect{metrics.left, metrics.top,
+ includingTrailingSpace ? metrics.widthIncludingTrailingWhitespace
+ : metrics.width,
+ metrics.height};
+}
+
+std::vector<Rect> DWriteTextLayout::TextRangeRect(
+ const TextRange& text_range_arg) {
+ if (text_range_arg.count == 0) {
+ return {};
+ }
+ const auto text_range = text_range_arg.Normalize();
+
+ DWRITE_TEXT_METRICS text_metrics;
+ ThrowIfFailed(text_layout_->GetMetrics(&text_metrics));
+ const auto metrics_count =
+ text_metrics.lineCount * text_metrics.maxBidiReorderingDepth;
+
+ std::vector<DWRITE_HIT_TEST_METRICS> hit_test_metrics(metrics_count);
+ UINT32 actual_count;
+ ThrowIfFailed(text_layout_->HitTestTextRange(
+ static_cast<UINT32>(text_range.position),
+ static_cast<UINT32>(text_range.count), 0, 0, hit_test_metrics.data(),
+ metrics_count, &actual_count));
+
+ hit_test_metrics.erase(hit_test_metrics.cbegin() + actual_count,
+ hit_test_metrics.cend());
+
+ std::vector<Rect> result;
+ result.reserve(actual_count);
+
+ for (const auto& metrics : hit_test_metrics) {
+ result.push_back(
+ Rect{metrics.left, metrics.top, metrics.width, metrics.height});
+ }
+
+ return result;
+}
+
+Point DWriteTextLayout::TextSinglePoint(Index position, bool trailing) {
+ DWRITE_HIT_TEST_METRICS metrics;
+ FLOAT left;
+ FLOAT top;
+ ThrowIfFailed(text_layout_->HitTestTextPosition(static_cast<UINT32>(position),
+ static_cast<BOOL>(trailing),
+ &left, &top, &metrics));
+ return Point{left, top};
+}
+
+TextHitTestResult DWriteTextLayout::HitTest(const Point& point) {
+ BOOL trailing;
+ BOOL inside;
+ DWRITE_HIT_TEST_METRICS metrics;
+
+ ThrowIfFailed(text_layout_->HitTestPoint(point.x, point.y, &trailing, &inside,
+ &metrics));
+
+ TextHitTestResult result;
+ result.position = metrics.textPosition;
+ result.trailing = trailing != 0;
+ result.insideText = inside != 0;
+ return result;
+}
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/WindowPainter.cpp b/src/win/graphics/direct/WindowPainter.cpp
new file mode 100644
index 00000000..c88667b6
--- /dev/null
+++ b/src/win/graphics/direct/WindowPainter.cpp
@@ -0,0 +1,20 @@
+#include "cru/win/graphics/direct/WindowPainter.hpp"
+
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
+#include "cru/win/graphics/direct/WindowRenderTarget.hpp"
+
+namespace cru::platform::graphics::win::direct {
+D2DWindowPainter::D2DWindowPainter(D2DWindowRenderTarget* render_target)
+ : D2DPainter(render_target->GetD2D1DeviceContext()),
+ render_target_(render_target) {
+ render_target_->GetD2D1DeviceContext()->BeginDraw();
+}
+
+D2DWindowPainter::~D2DWindowPainter() { EndDraw(); }
+
+void D2DWindowPainter::DoEndDraw() {
+ ThrowIfFailed(render_target_->GetD2D1DeviceContext()->EndDraw());
+ render_target_->Present();
+}
+} // namespace cru::platform::graphics::win::direct
diff --git a/src/win/graphics/direct/WindowRenderTarget.cpp b/src/win/graphics/direct/WindowRenderTarget.cpp
new file mode 100644
index 00000000..7479ae24
--- /dev/null
+++ b/src/win/graphics/direct/WindowRenderTarget.cpp
@@ -0,0 +1,81 @@
+#include "cru/win/graphics/direct/WindowRenderTarget.hpp"
+
+#include "cru/win/graphics/direct/Exception.hpp"
+#include "cru/win/graphics/direct/Factory.hpp"
+
+namespace cru::platform::graphics::win::direct {
+D2DWindowRenderTarget::D2DWindowRenderTarget(
+ gsl::not_null<DirectGraphFactory*> factory, HWND hwnd)
+ : factory_(factory), hwnd_(hwnd) {
+ const auto d3d11_device = factory->GetD3D11Device();
+ const auto dxgi_factory = factory->GetDxgiFactory();
+
+ d2d1_device_context_ = factory->CreateD2D1DeviceContext();
+ d2d1_device_context_->SetUnitMode(D2D1_UNIT_MODE_DIPS);
+
+ // Allocate a descriptor.
+ DXGI_SWAP_CHAIN_DESC1 swap_chain_desc;
+ swap_chain_desc.Width = 0; // use automatic sizing
+ swap_chain_desc.Height = 0;
+ swap_chain_desc.Format =
+ DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format
+ swap_chain_desc.Stereo = false;
+ swap_chain_desc.SampleDesc.Count = 1; // don't use multi-sampling
+ swap_chain_desc.SampleDesc.Quality = 0;
+ swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swap_chain_desc.BufferCount = 2; // use double buffering to enable flip
+ swap_chain_desc.Scaling = DXGI_SCALING_NONE;
+ swap_chain_desc.SwapEffect =
+ DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect
+ swap_chain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
+ swap_chain_desc.Flags = 0;
+
+ // Get the final swap chain for this window from the DXGI factory.
+ ThrowIfFailed(dxgi_factory->CreateSwapChainForHwnd(
+ d3d11_device, hwnd, &swap_chain_desc, nullptr, nullptr,
+ &dxgi_swap_chain_));
+
+ CreateTargetBitmap();
+}
+
+void D2DWindowRenderTarget::SetDpi(float x, float y) {
+ d2d1_device_context_->SetDpi(x, y);
+}
+
+void D2DWindowRenderTarget::ResizeBuffer(const int width, const int height) {
+ // In order to resize buffer, we need to untarget the buffer first.
+ d2d1_device_context_->SetTarget(nullptr);
+ target_bitmap_ = nullptr;
+ ThrowIfFailed(dxgi_swap_chain_->ResizeBuffers(0, width, height,
+ DXGI_FORMAT_UNKNOWN, 0));
+ CreateTargetBitmap();
+}
+
+void D2DWindowRenderTarget::Present() {
+ ThrowIfFailed(dxgi_swap_chain_->Present(1, 0));
+}
+
+void D2DWindowRenderTarget::CreateTargetBitmap() {
+ Expects(target_bitmap_ == nullptr); // target bitmap must not exist.
+
+ // Direct2D needs the dxgi version of the backbuffer surface pointer.
+ Microsoft::WRL::ComPtr<IDXGISurface> dxgi_back_buffer;
+ ThrowIfFailed(
+ dxgi_swap_chain_->GetBuffer(0, IID_PPV_ARGS(&dxgi_back_buffer)));
+
+ float dpi_x, dpi_y;
+ d2d1_device_context_->GetDpi(&dpi_x, &dpi_y);
+
+ auto bitmap_properties = D2D1::BitmapProperties1(
+ D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE),
+ dpi_x, dpi_y);
+
+ // Get a D2D surface from the DXGI back buffer to use as the D2D render
+ // target.
+ ThrowIfFailed(d2d1_device_context_->CreateBitmapFromDxgiSurface(
+ dxgi_back_buffer.Get(), &bitmap_properties, &target_bitmap_));
+
+ d2d1_device_context_->SetTarget(target_bitmap_.Get());
+}
+} // namespace cru::platform::graphics::win::direct