Skip to content

Instantly share code, notes, and snippets.

@JustasMasiulis
Last active April 19, 2023 05:53
Show Gist options
  • Save JustasMasiulis/bee43f5a1280c2359889898f6d093b80 to your computer and use it in GitHub Desktop.
Save JustasMasiulis/bee43f5a1280c2359889898f6d093b80 to your computer and use it in GitHub Desktop.
Simple d2d1 overlay
#include "overlay_drawer.hpp"
#include <iostream>
#include <random>
#include <chrono>
std::minstd_rand gen(std::random_device{}());
const std::uniform_real_distribution<float> uni_col(0.2f, 1.f);
const std::uniform_int_distribution<int> uni_pos(100, 1000);
const std::uniform_int_distribution<int> uni_sz(30, 100);
const auto pos = [&]() { return static_cast<float>(uni_pos(gen)); };
const auto sz = [&]() { return static_cast<float>(uni_sz(gen)); };
D2D1::ColorF gen_color()
{
return { uni_col(gen), uni_col(gen), uni_col(gen), uni_col(gen) };
}
int main() try
{
overlay::drawer d{ overlay::create_basic_window("test") };
// font_resource
auto font{ d.create_font(L"Arial", 25) };
// layer_resource
auto layer = d.create_layer();
// text_resource
auto watermark = d.create_text(font, L"simple d2d1 overlay");
watermark.align(overlay::text_align::right);
std::uint64_t frametime = 1;
std::uint64_t frametime_sum = 1;
std::uint64_t frame_count = 1;
std::uint64_t avg_fps = 1;
while (true) {
using std::chrono::steady_clock;
using std::chrono::microseconds;
using std::chrono::duration_cast;
const auto begin = steady_clock::now();
// message pump -------------------------------------------------------
d.pump_messages();
// average fps --------------------------------------------------------
font.align(overlay::text_align::right);
font.draw(L"current fps: " + std::to_wstring(1000000 / frametime)
+ L"\nstabilized: " + std::to_wstring(1000000 / (frametime_sum / frame_count))
+ L"\naverage over 1000 frames: " + std::to_wstring(avg_fps)
, { 0, 0 }
, { 1.f, 0.2f, 1.f, 0.9f });
watermark.draw({ 500,0 }, { 0.6f, 0.2f, 1.f, 0.9f });
{
// needs to get destroyed before call to display
auto clip = layer.make_guard({ 0, 100, 10000, 10000 });
// circles ------------------------------------------------------------
for (int i = 0; i < 100; ++i)
d.fill_circle({ pos(), pos() }, sz(), gen_color());
// rectangles ---------------------------------------------------------
for (int i = 0; i < 100; ++i) {
const D2D1_POINT_2F start{ pos() + 700, pos() };
d.fill_rect(start, { start.x + sz(), start.y + sz() }, gen_color());
}
// lines --------------------------------------------------------------
for (int i = 0; i < 100; ++i)
d.line({ pos(), pos() }, { pos(), pos() }, gen_color());
// text ---------------------------------------------------------------
font.align(overlay::text_align::center);
POINT p;
GetCursorPos(&p);
for (int i = 0; i < 100; ++i)
font.draw(L"BLAZIN FAST", { p.x + pos(), p.y + pos() }, gen_color());
}
d.display();
frametime = static_cast<std::uint64_t>(
duration_cast<microseconds>(steady_clock::now() - begin).count());
frametime_sum += frametime;
if (++frame_count == 1000) {
avg_fps = 1000000 / (frametime_sum / 1000);
frametime_sum = 1;
frame_count = 1;
}
}
}
catch (std::exception& e)
{
std::cerr << e.what();
}
/*
* Copyright 2017 Justas Masiulis
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "overlay_drawer.hpp"
#include <system_error>
#include <d2d1_3helper.h>
#include <d3d11_2.h>
using namespace overlay;
[[noreturn]]
inline void throw_error(const int code, const char* message)
{
throw std::system_error(std::error_code(code, std::system_category()), message);
}
[[noreturn]]
inline void throw_last_error(const char* msg)
{
throw_error(static_cast<int>(GetLastError()), msg);
}
inline void HR(const HRESULT result, const char* msg)
{
if (result != S_OK)
throw_error(result, msg);
}
ComPtr<IDXGISwapChain1> create_swap_chain(const HWND window
, ComPtr<IDXGIDevice>& dxgi_device)
{
ComPtr<ID3D11Device> d3_device;
HR(D3D11CreateDevice(nullptr
, D3D_DRIVER_TYPE_HARDWARE
, nullptr
, D3D11_CREATE_DEVICE_BGRA_SUPPORT
, nullptr
, 0
, D3D11_SDK_VERSION
, &d3_device
, nullptr
, nullptr), "D3D11CreateDevice");
HR(d3_device.As(&dxgi_device), "ComPtr<ID3D11Device>::As");
ComPtr<IDXGIFactory2> dx_factory;
HR(CreateDXGIFactory2(0 // do not load the debug dll
, __uuidof(IDXGIFactory2)
, reinterpret_cast<void**>(dx_factory.GetAddressOf()))
, "CreateDXGIFactory2");
DXGI_SWAP_CHAIN_DESC1 description = { 0 };
description.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
description.BufferCount = 2;
description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
description.SampleDesc.Count = 1;
RECT rect;
if (!GetClientRect(window, &rect))
throw_last_error("GetClientRect");
description.Width = static_cast<UINT>(rect.right - rect.left);
description.Height = static_cast<UINT>(rect.bottom - rect.top);
ComPtr<IDXGISwapChain1> schain;
HR(dx_factory->CreateSwapChainForComposition(dxgi_device.Get()
, &description
, nullptr
, schain.GetAddressOf()), "CreateSwapChainForComposition");
return schain;
}
ComPtr<ID2D1DeviceContext> create_device_ctx(ComPtr<IDXGIDevice>& dxgi_device)
{
// if you manually close the window a breakpoint will be triggered
// and say that you have leaks because the destructors are not run
const D2D1_FACTORY_OPTIONS options = { D2D1_DEBUG_LEVEL_NONE };
ComPtr<ID2D1Factory2> d2_factory;
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED
, options
, d2_factory.GetAddressOf())
, "D2D1CreateFactory");
// create the d2d device that links back to the d3d device
ComPtr<ID2D1Device1> d2_device;
HR(d2_factory->CreateDevice(dxgi_device.Get(), d2_device.GetAddressOf()),
"ID2D1Factory2::CreateDevice");
// Create the d2d device context that is the actual render target
// and exposes drawing commands
ComPtr<ID2D1DeviceContext> dev_context;
HR(d2_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS
, dev_context.GetAddressOf()), "ID2D1Device1::CreateDeviceContext");
return dev_context;
}
ComPtr<IDCompositionTarget> create_composition_target(const HWND window
, ComPtr<IDXGIDevice>& dxgi_device
, ComPtr<ID2D1DeviceContext>& dev_context
, ComPtr<IDXGISwapChain1>& swap_chain)
{
// retrieve back buffer
ComPtr<IDXGISurface2> surface;
HR(swap_chain->GetBuffer(0 // index
, __uuidof(IDXGISurface2)
, reinterpret_cast<void**>(surface.GetAddressOf()))
, "IDXGISwapChain1::GetBuffer");
// create a d2d bitmap that points to the swap chain surface
D2D1_BITMAP_PROPERTIES1 properties = {};
properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
properties.pixelFormat.format = DXGI_FORMAT_R8G8B8A8_UNORM;
properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET
| D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
ComPtr<ID2D1Bitmap1> bitmap;
HR(dev_context->CreateBitmapFromDxgiSurface(surface.Get()
, properties
, bitmap.GetAddressOf()), "ID2D1DeviceContext::CreateBitmapFromDxgiSurface");
dev_context->SetTarget(bitmap.Get());
ComPtr<IDCompositionDevice> comp_device;
HR(DCompositionCreateDevice(dxgi_device.Get()
, __uuidof(IDCompositionDevice)
, reinterpret_cast<void**>(comp_device.GetAddressOf()))
, "DCompositionCreateDevice");
ComPtr<IDCompositionTarget> comp_target;
HR(comp_device->CreateTargetForHwnd(window
, true // topmost
, comp_target.GetAddressOf()), "IDCompositionTarget::CreateTargetForHwnd");
ComPtr<IDCompositionVisual> visual;
HR(comp_device->CreateVisual(visual.GetAddressOf())
, "IDCompositionDevice::CreateVisual");
HR(visual->SetContent(swap_chain.Get())
, "IDCompositionVisual::SetContent");
HR(comp_target->SetRoot(visual.Get())
, "IDCompositionTarget::SetRoot");
HR(comp_device->Commit()
, "IDCompositionDevice::Commit");
return comp_target;
}
layer_resource::layer_resource(ID2D1DeviceContext* dc, ComPtr<ID2D1Layer>&& layer)
: _dc(dc)
, _layer(std::move(layer)) {}
void layer_resource::push(const D2D1_RECT_F& rect, float opacity)
{
// Push the layer with the content bounds.
_dc->PushLayer(D2D1::LayerParameters(rect
, nullptr
, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE
, D2D1::IdentityMatrix()
, opacity
, nullptr
, D2D1_LAYER_OPTIONS_NONE)
, _layer.Get());
}
void layer_resource::pop()
{
_dc->PopLayer();
}
layer_resource::guard_t layer_resource::make_guard(const D2D1_RECT_F& rect, float opacity)
{
push(rect, opacity);
return guard_t{ _dc };
}
ID2D1SolidColorBrush* drawer::local_brush(const D2D1::ColorF & color)
{
static thread_local auto brush = [](ComPtr<ID2D1DeviceContext>& dev_context) {
ComPtr<ID2D1SolidColorBrush> local_brush;
HR(dev_context->CreateSolidColorBrush({ 0,0,0,0 }
, local_brush.GetAddressOf()), "CreateSolidColorBrush");
return local_brush;
}(_dev_context);
brush->SetColor(color);
return brush.Get();
}
drawer::drawer(HWND window)
: _window_handle(window)
{
ComPtr<IDXGIDevice> dxgi_device;
_swap_chain = create_swap_chain(window, dxgi_device);
_dev_context = create_device_ctx(dxgi_device);
_comp_target = create_composition_target(window, dxgi_device, _dev_context, _swap_chain);
// create dwrite factory to generate text layouts
HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED
, __uuidof(IDWriteFactory)
, reinterpret_cast<IUnknown**>(_dwrite_factory.GetAddressOf()))
, "DWriteCreateFactory");
// we need to begin drawing immediately because we exposed essentially
// only the end method
_dev_context->BeginDraw();
_dev_context->Clear();
}
drawer::~drawer() noexcept
{
_dev_context->EndDraw();
}
void drawer::display()
{
HR(_dev_context->EndDraw(), "ID2D1DeviceContext::EndDraw");
// should probably put error handling here but this can fail for stupid reasons
_swap_chain->Present(0, 0);
_dev_context->BeginDraw();
_dev_context->Clear();
}
void drawer::pump_messages() const
{
MSG m;
if (PeekMessageA(&m, _window_handle, 0, 0, PM_REMOVE)) {
TranslateMessage(&m);
DispatchMessageA(&m);
}
}
void drawer::circle(const D2D1_POINT_2F& center
, float radius
, const D2D1::ColorF& color
, float width)
{
ellipse(center, radius, radius, color, width);
}
void drawer::fill_circle(const D2D1_POINT_2F& center
, float radius
, const D2D1::ColorF& color)
{
fill_ellipse(center, radius, radius, color);
}
void drawer::ellipse(const D2D1_POINT_2F& center
, float radius_x
, float radius_y
, const D2D1::ColorF& color
, float width)
{
_dev_context->DrawEllipse({ center, radius_x, radius_y }, local_brush(color), width);
}
void drawer::fill_ellipse(const D2D1_POINT_2F& center
, float radius_x
, float radius_y
, const D2D1::ColorF& color)
{
_dev_context->FillEllipse({ center, radius_x, radius_y }, local_brush(color));
}
void drawer::rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color, float width)
{
_dev_context->DrawRectangle(rect, local_brush(color), width);
}
void drawer::fill_rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color)
{
_dev_context->FillRectangle(rect, local_brush(color));
}
void overlay::drawer::rounded_rect(const D2D1_RECT_F & rect, const D2D1::ColorF & color, float rounding, float width)
{
_dev_context->DrawRoundedRectangle({ rect, rounding, rounding, }, local_brush(color), width);
}
void overlay::drawer::fill_rounded_rect(const D2D1_RECT_F & rect, const D2D1::ColorF & color, float rounding)
{
_dev_context->FillRoundedRectangle({ rect, rounding, rounding, }, local_brush(color));
}
void drawer::line(const D2D1_POINT_2F& start
, const D2D1_POINT_2F& end
, const D2D1::ColorF& color
, float width)
{
_dev_context->DrawLine(start, end, local_brush(color), width);
}
font_resource drawer::create_font(const std::wstring& font_family
, float size
, DWRITE_FONT_WEIGHT weight
, DWRITE_FONT_STYLE style)
{
ComPtr<IDWriteTextFormat> format;
HR(_dwrite_factory->CreateTextFormat(font_family.c_str()
, nullptr
, weight
, style
, DWRITE_FONT_STRETCH_NORMAL
, size
, L""
, format.GetAddressOf())
, "IDWriteTextFormat::CreateTextFormat");
return { std::move(format), _dev_context.Get() };
}
text_resource overlay::drawer::create_text(const font_resource & wrapper
, const std::wstring & text)
{
ComPtr<IDWriteTextLayout> layout;
_dwrite_factory->CreateTextLayout(text.c_str()
, static_cast<UINT>(text.size())
, wrapper.format()
, 0
, 0
, layout.GetAddressOf());
return { std::move(layout), _dev_context.Get() };
}
layer_resource drawer::create_layer()
{
ComPtr<ID2D1Layer> layer;
HR(_dev_context->CreateLayer(layer.GetAddressOf())
, "ID2D1DeviceContext::CreateLayer");
return { _dev_context.Get(), std::move(layer) };
}
drawer::translation_guard drawer::make_translation(const D2D1_POINT_2F & point, bool accumulate)
{
if (accumulate) {
auto translation = get_transform();
translation._31 += point.x;
translation._32 += point.y;
_dev_context->SetTransform(translation);
return translation_guard(_dev_context.Get(),
{ translation._31 - point.x, translation._32 - point.y });
}
else {
_dev_context->SetTransform(D2D1::Matrix3x2F::Translation(point.x, point.y));
return translation_guard(_dev_context.Get());
}
}
D2D1_POINT_2F drawer::get_translation() const
{
const auto mat = get_transform();
return { mat._31, mat._32 };
}
D2D1_MATRIX_3X2_F drawer::get_transform() const
{
D2D1_MATRIX_3X2_F mat;
_dev_context->GetTransform(&mat);
return mat;
}
font_resource::font_resource(ComPtr<IDWriteTextFormat>&& format
, ID2D1DeviceContext* context)
: _dc(context)
, _format(std::move(format))
{
// since we set the size of our text rect to 0 we need to disable wrapping
HR(_format->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP)
, "IDWriteTextFormat::SetWordWrapping");
HR(_dc->CreateSolidColorBrush({ 0,0,0,0 }, _brush.GetAddressOf())
, "CreateSolidColorBrush");
}
void font_resource::align(text_align horizontal_alignment)
{
HR(_format->SetTextAlignment(static_cast<DWRITE_TEXT_ALIGNMENT>(horizontal_alignment))
, "IDWriteTextFormat::SetTextAlignment");
}
void overlay::font_resource::align_vert(vert_text_align vertical_alignment)
{
HR(_format->SetParagraphAlignment(static_cast<DWRITE_PARAGRAPH_ALIGNMENT>(vertical_alignment))
, "IDWriteTextFormat::SetParagraphAlignment");
}
void font_resource::draw(const std::wstring& text
, const D2D1_POINT_2F pos
, const D2D1::ColorF& color)
{
_brush->SetColor(color);
_dc->DrawTextA(text.c_str()
, static_cast<UINT32>(text.size())
, _format.Get()
, { pos.x, pos.y, pos.x, pos.y }
, _brush.Get());
}
HWND overlay::create_basic_window(const char* window_name
, int width
, int height
, WNDPROC wndproc)
{
WNDCLASSA wc = { 0 };
wc.lpszClassName = window_name;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = wndproc;
const auto class_atom = RegisterClassA(&wc);
if (!class_atom)
throw_last_error("RegisterClassA");
const auto flags = WS_EX_NOREDIRECTIONBITMAP
| WS_EX_LAYERED
| WS_EX_TRANSPARENT
| WS_EX_TOPMOST;
if (width == -1)
width = GetSystemMetrics(SM_CXSCREEN);
if (height == -1)
height = GetSystemMetrics(SM_CYSCREEN);
const auto window = CreateWindowExA(flags
, wc.lpszClassName
, window_name
, WS_VISIBLE | WS_POPUP
, -1
, -1
, width
, height
, nullptr
, nullptr
, nullptr
, nullptr);
if (!window)
throw_last_error("CreateWindowExA");
return window;
}
overlay::text_resource::text_resource(ComPtr<IDWriteTextLayout>&& layout, ID2D1DeviceContext * context)
: _dc(context)
, _layout(std::move(layout))
{
HR(_dc->CreateSolidColorBrush({ 0,0,0,0 }, _brush.GetAddressOf())
, "CreateSolidColorBrush");
DWRITE_TEXT_METRICS metric;
HR(_layout->GetMetrics(&metric), "IDWriteTextLayout::GetMetrics");
_width = metric.width;
_height = metric.height;
}
void text_resource::align(text_align horizontal_alignment)
{
HR(_layout->SetTextAlignment(static_cast<DWRITE_TEXT_ALIGNMENT>(horizontal_alignment))
, "IDWriteTextLayout::SetTextAlignment");
}
void text_resource::align_vert(vert_text_align vertical_alignment)
{
HR(_layout->SetParagraphAlignment(static_cast<DWRITE_PARAGRAPH_ALIGNMENT>(vertical_alignment))
, "IDWriteTextLayout::SetParagraphAlignment");
}
void text_resource::draw(const D2D1_POINT_2F pos, const D2D1::ColorF & color)
{
_brush->SetColor(color);
_dc->DrawTextLayout(pos, _layout.Get(), _brush.Get());
}
void drawer::_translation_destructor::operator()(ID2D1DeviceContext * dc)
{
dc->SetTransform(D2D1::Matrix3x2F::Translation(previous.x, previous.y));
}
/*
* Copyright 2017 Justas Masiulis
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef JM_OVERLAY_DRAWER_HPP
#define JM_OVERLAY_DRAWER_HPP
#define NOMINMAX
#include <string>
#include <memory>
#include <dwrite_3.h>
#include <dcomp.h>
#include <wrl.h>
// pragma comment is not portable
#ifdef _MSC_VER
#pragma comment(lib, "dxgi")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dcomp")
#pragma comment(lib, "dwrite.lib")
#pragma comment(lib, "user32.lib")
#endif
namespace overlay {
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
inline LRESULT CALLBACK _default_wndproc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
{
if (message == WM_DESTROY)
{
PostQuitMessage(0);
return 0;
}
return DefWindowProc(window, message, wparam, lparam);
}
// value of -1 for width and height means the window will be made fullscreen
HWND create_basic_window(const char* window_name
, int width = -1
, int height = -1
, WNDPROC wndproc = _default_wndproc);
enum class text_align {
// on the left of drawing position
left = DWRITE_TEXT_ALIGNMENT_TRAILING,
center = DWRITE_TEXT_ALIGNMENT_CENTER,
// on the right of drawing position
right = DWRITE_TEXT_ALIGNMENT_LEADING
};
enum class vert_text_align {
// above the drawing position
top = DWRITE_PARAGRAPH_ALIGNMENT_FAR,
center = DWRITE_PARAGRAPH_ALIGNMENT_CENTER,
// below the drawing position
bottom = DWRITE_PARAGRAPH_ALIGNMENT_NEAR
};
class font_resource {
// owned by overlay drawer
ID2D1DeviceContext* _dc;
ComPtr<IDWriteTextFormat> _format;
ComPtr<ID2D1SolidColorBrush> _brush;
public:
font_resource(ComPtr<IDWriteTextFormat>&& format, ID2D1DeviceContext* context);
void align(text_align horizontal_alignment);
void align_vert(vert_text_align vertical_alignment);
void draw(const std::wstring& text
, const D2D1_POINT_2F pos
, const D2D1::ColorF& color);
IDWriteTextFormat* format() const noexcept { return _format.Get(); }
// TextFormat doesn't really allow to change much of it.
// only getters about the params with which it was created are possible
};
class text_resource {
// owned by overlay drawer
ID2D1DeviceContext* _dc;
ComPtr<IDWriteTextLayout> _layout;
ComPtr<ID2D1SolidColorBrush> _brush;
float _width;
float _height;
public:
text_resource(ComPtr<IDWriteTextLayout>&& layout, ID2D1DeviceContext* context);
void align(text_align horizontal_alignment);
void align_vert(vert_text_align vertical_alignment);
void draw(const D2D1_POINT_2F pos
, const D2D1::ColorF& color);
float width() const noexcept { return _width; }
float height() const noexcept { return _height; }
IDWriteTextLayout* layout() const noexcept { return _layout.Get(); }
};
class layer_resource {
ID2D1DeviceContext* _dc;
ComPtr<ID2D1Layer> _layer;
struct _popper {
void operator()(ID2D1DeviceContext* dc) const { dc->PopLayer(); }
};
public:
layer_resource(ID2D1DeviceContext* dc, ComPtr<ID2D1Layer>&& layer);
using guard_t = std::unique_ptr <ID2D1DeviceContext, _popper>;
void push(const D2D1_RECT_F& rect, float opacity);
void pop();
guard_t make_guard(const D2D1_RECT_F& rect, float opacity = 1.f);
};
class drawer {
// must be preserved for drawing
ComPtr<IDXGISwapChain1> _swap_chain;
ComPtr<ID2D1DeviceContext> _dev_context;
ComPtr<IDCompositionTarget> _comp_target;
// needed for font layout creation
ComPtr<IDWriteFactory> _dwrite_factory;
// can be retrieved only from swapchain that was created with ...ForHwnd function
HWND _window_handle;
// thread safe buffer storage
ID2D1SolidColorBrush* local_brush(const D2D1::ColorF &color);
struct _translation_destructor {
D2D1_POINT_2F previous{ 0.f,0.f };
void operator()(ID2D1DeviceContext* dc);
};
public:
using translation_guard = std::unique_ptr<ID2D1DeviceContext, _translation_destructor>;
explicit drawer(HWND window);
// need to call EndDraw
~drawer() noexcept;
HWND window_handle() const noexcept { return _window_handle; }
// call this after finishing to draw figures
void display();
void pump_messages() const;
void circle(const D2D1_POINT_2F& center, float radius, const D2D1::ColorF& color, float width = 1.f);
void fill_circle(const D2D1_POINT_2F& center, float radius, const D2D1::ColorF& color);
void ellipse(const D2D1_POINT_2F& center
, float radius_x
, float radius_y
, const D2D1::ColorF& color
, float width = 1.f);
void fill_ellipse(const D2D1_POINT_2F& center
, float radius_x
, float radius_y
, const D2D1::ColorF& color);
void rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color, float width = 1);
void fill_rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color);
void rect(const D2D1_POINT_2F& start
, const D2D1_POINT_2F& end
, const D2D1::ColorF& color
, float width = 1.f)
{
rect({ start.x, start.y, end.x, end.y }, color, width);
}
void fill_rect(const D2D1_POINT_2F& start
, const D2D1_POINT_2F& end
, const D2D1::ColorF& color)
{
fill_rect({ start.x, start.y, end.x, end.y }, color);
}
void rounded_rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color, float rounding = 1, float width = 1);
void fill_rounded_rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color, float rounding = 1);
void line(const D2D1_POINT_2F& start
, const D2D1_POINT_2F& end
, const D2D1::ColorF& color
, float width = 1.f);
// the parameters cannot be changed after creation.
// I would recommend not creating multiple copies of same font, but
// they seem to be cached by DXGI so it may be fine
font_resource create_font(const std::wstring& font_family
, float size
, DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_REGULAR
, DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL);
text_resource create_text(const font_resource& wrapper, const std::wstring& text);
layer_resource create_layer();
/// if accumulate is set to true previous translation value is added
translation_guard make_translation(const D2D1_POINT_2F& point, bool accumulate = true);
D2D1_POINT_2F get_translation() const;
D2D1_MATRIX_3X2_F get_transform() const;
};
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment