Direct2D в Rainmeter

от автора

Доброго времени суток, Хабр. Всегда хотел свободы в Rainmeter’е. Однотипные скины, простые плагины — не то.

Сегодня я расскажу как получить полную власть над Rainmeter’ом.

Залезем в исходники Rainmeter’а и посмотрим, как же там всё так красиво рисуется…

Нас интересует файл Library/Meter.cpp

Скрытый текст

bool Meter::Draw(Gfx::Canvas& canvas) { 	if (IsHidden()) return false;  	canvas.SetAntiAliasing(m_AntiAlias);  	if (m_SolidColor.a != 0.0f || m_SolidColor2.a != 0.0f) 	{ 		const FLOAT x = (FLOAT)GetX(); 		const FLOAT y = (FLOAT)GetY();  		const D2D1_RECT_F r = D2D1::RectF(x, y, x + (FLOAT)m_W, y + (FLOAT)m_H);  		if (m_SolidColor.r == m_SolidColor2.r && m_SolidColor.g == m_SolidColor2.g &&  			m_SolidColor.b == m_SolidColor2.b && m_SolidColor.a == m_SolidColor2.a) 		{ 			canvas.FillRectangle(r, m_SolidColor); 		} 		else 		{ 			canvas.FillGradientRectangle(r, m_SolidColor, m_SolidColor2, (FLOAT)m_SolidAngle); 		} 	}  	if (m_SolidBevel != BEVELTYPE_NONE) 	{ 		D2D1_COLOR_F lightColor = D2D1::ColorF(D2D1::ColorF::White); 		D2D1_COLOR_F darkColor = D2D1::ColorF(D2D1::ColorF::Black); 		 		if (m_SolidBevel == BEVELTYPE_DOWN) 		{ 			lightColor = D2D1::ColorF(D2D1::ColorF::Black); 			darkColor = D2D1::ColorF(D2D1::ColorF::White); 		}  		// The bevel is drawn outside the meter 		const FLOAT x = (FLOAT)GetX(); 		const FLOAT y = (FLOAT)GetY(); 		const D2D1_RECT_F rect = D2D1::RectF(x - 2.0f, y - 2.0f, x + (FLOAT)m_W + 2.0f, y + (FLOAT)m_H + 2.0f); 		DrawBevel(canvas, rect, lightColor, darkColor); 	}  	return true; } 

Теперь узнаем что же такое этот Canvas

Common/Gfx/Canvas.cpp

/* Copyright (C) 2013 Rainmeter Project Developers  *  * This Source Code Form is subject to the terms of the GNU General Public  * License; either version 2 of the License, or (at your option) any later  * version. If a copy of the GPL was not distributed with this file, You can  * obtain one at <https://www.gnu.org/licenses/gpl-2.0.html>. */  #include "StdAfx.h" #include "Canvas.h" #include "TextFormatD2D.h" #include "D2DBitmap.h" #include "RenderTexture.h" #include "Util/D2DUtil.h" #include "Util/DWriteFontCollectionLoader.h" #include "../../Library/Util.h" #include "../../Library/Logger.h"  namespace Gfx {  UINT Canvas::c_Instances = 0; D3D_FEATURE_LEVEL Canvas::c_FeatureLevel; Microsoft::WRL::ComPtr<ID3D11Device> Canvas::c_D3DDevice; Microsoft::WRL::ComPtr<ID3D11DeviceContext> Canvas::c_D3DContext; Microsoft::WRL::ComPtr<ID2D1Device> Canvas::c_D2DDevice; Microsoft::WRL::ComPtr<IDXGIDevice1> Canvas::c_DxgiDevice; Microsoft::WRL::ComPtr<ID2D1Factory1> Canvas::c_D2DFactory; Microsoft::WRL::ComPtr<IDWriteFactory1> Canvas::c_DWFactory; Microsoft::WRL::ComPtr<IWICImagingFactory> Canvas::c_WICFactory;  Canvas::Canvas() : 	m_W(0), 	m_H(0), 	m_MaxBitmapSize(0U), 	m_IsDrawing(false), 	m_EnableDrawAfterGdi(false), 	m_TextAntiAliasing(false), 	m_CanUseAxisAlignClip(true) { 	Initialize(true); }  Canvas::~Canvas() { 	Finalize(); }  bool Canvas::LogComError(HRESULT hr) { 	_com_error err(hr); 	LogErrorF(L"Error 0x%08x: %s", hr, err.ErrorMessage()); 	return false; }  bool Canvas::Initialize(bool hardwareAccelerated) { 	++c_Instances; 	if (c_Instances == 1U) 	{ 		// Required for Direct2D interopability. 		UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;  #ifdef _DEBUG 		creationFlags |= D3D11_CREATE_DEVICE_DEBUG; #endif  		auto tryCreateContext = [&](D3D_DRIVER_TYPE driverType, 			const D3D_FEATURE_LEVEL* levels, UINT numLevels) 		{ 			return D3D11CreateDevice( 				nullptr, 				driverType, 				nullptr, 				creationFlags, 				levels, 				numLevels, 				D3D11_SDK_VERSION, 				c_D3DDevice.GetAddressOf(), 				&c_FeatureLevel, 				c_D3DContext.GetAddressOf()); 		};  		// D3D selects the best feature level automatically and sets it 		// to |c_FeatureLevel|. First, we try to use the hardware driver 		// and if that fails, we try the WARP rasterizer for cases 		// where there is no graphics card or other failures. 		const D3D_FEATURE_LEVEL 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 		};  		HRESULT hr = E_FAIL; 		if (hardwareAccelerated) 		{ 			hr = tryCreateContext(D3D_DRIVER_TYPE_HARDWARE, levels, _countof(levels)); 			if (hr == E_INVALIDARG) 			{ 				hr = tryCreateContext(D3D_DRIVER_TYPE_HARDWARE, &levels[1], _countof(levels) - 1); 			} 		}  		if (FAILED(hr)) 		{ 			hr = tryCreateContext(D3D_DRIVER_TYPE_WARP, nullptr, 0U); 			if (FAILED(hr)) return false; 		}  		hr = c_D3DDevice.As(&c_DxgiDevice); 		if (FAILED(hr)) return false;  		D2D1_FACTORY_OPTIONS fo = {}; #ifdef _DEBUG 		fo.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; #endif  		hr = D2D1CreateFactory( 			D2D1_FACTORY_TYPE_SINGLE_THREADED, 			fo, 			c_D2DFactory.GetAddressOf()); 		if (FAILED(hr)) return false;  		hr = c_D2DFactory->CreateDevice( 			c_DxgiDevice.Get(), 			c_D2DDevice.GetAddressOf()); 		if (FAILED(hr)) return false;  		hr = CoCreateInstance( 			CLSID_WICImagingFactory, 			nullptr, 			CLSCTX_INPROC_SERVER, 			IID_IWICImagingFactory, 			(LPVOID*)c_WICFactory.GetAddressOf()); 		if (FAILED(hr)) return false;  		hr = DWriteCreateFactory( 			DWRITE_FACTORY_TYPE_SHARED, 			__uuidof(c_DWFactory), 			(IUnknown**)c_DWFactory.GetAddressOf()); 		if (FAILED(hr)) return false;  		hr = c_DWFactory->RegisterFontCollectionLoader(Util::DWriteFontCollectionLoader::GetInstance()); 		if (FAILED(hr)) return false; 	}  	return true; }  void Canvas::Finalize() { 	--c_Instances; 	if (c_Instances == 0U) 	{ 		c_D3DDevice.Reset(); 		c_D3DContext.Reset(); 		c_D2DDevice.Reset(); 		c_DxgiDevice.Reset(); 		c_D2DFactory.Reset(); 		c_WICFactory.Reset();  		if (c_DWFactory) 		{ 			c_DWFactory->UnregisterFontCollectionLoader(Util::DWriteFontCollectionLoader::GetInstance()); 			c_DWFactory.Reset(); 		} 	} }  bool Canvas::InitializeRenderTarget(HWND hwnd) { 	DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 }; 	swapChainDesc.Width = 1U; 	swapChainDesc.Height = 1U; 	swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; 	swapChainDesc.Stereo = false; 	swapChainDesc.SampleDesc.Count = 1U; 	swapChainDesc.SampleDesc.Quality = 0U; 	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 	swapChainDesc.BufferCount = 2U; 	swapChainDesc.Scaling = DXGI_SCALING_STRETCH; 	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; 	swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE; 	swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;  	Microsoft::WRL::ComPtr<IDXGIAdapter> dxgiAdapter; 	HRESULT hr = c_DxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf()); 	if (FAILED(hr)) return LogComError(hr);  	// Ensure that DXGI does not queue more than one frame at a time. 	hr = c_DxgiDevice->SetMaximumFrameLatency(1U); 	if (FAILED(hr)) return LogComError(hr);  	Microsoft::WRL::ComPtr<IDXGIFactory2> dxgiFactory; 	hr = dxgiAdapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf())); 	if (FAILED(hr)) return LogComError(hr);  	hr = dxgiFactory->CreateSwapChainForHwnd( 		c_DxgiDevice.Get(), 		hwnd, 		&swapChainDesc, 		nullptr, 		nullptr, 		m_SwapChain.ReleaseAndGetAddressOf()); 	if (FAILED(hr)) return LogComError(hr);  	hr = CreateRenderTarget(); 	if (FAILED(hr)) return LogComError(hr);  	return CreateTargetBitmap(0U, 0U); }  void Canvas::Resize(int w, int h) { 	// Truncate the size of the skin if it's too big. 	if (w > (int)m_MaxBitmapSize) w = (int)m_MaxBitmapSize; 	if (h > (int)m_MaxBitmapSize) h = (int)m_MaxBitmapSize;  	m_W = w; 	m_H = h;  	// Check if target, targetbitmap, backbuffer, swap chain are valid?  	// Unmap all resources tied to the swap chain. 	m_Target->SetTarget(nullptr); 	m_TargetBitmap.Reset(); 	m_BackBuffer.Reset();  	// Resize swap chain. 	HRESULT hr = m_SwapChain->ResizeBuffers( 		0U, 		(UINT)w, 		(UINT)h, 		DXGI_FORMAT_B8G8R8A8_UNORM, 		DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE); 	if (FAILED(hr)) return;  	CreateTargetBitmap((UINT32)w, (UINT32)h); }  bool Canvas::BeginDraw() { 	if (!m_Target) 	{ 		HRESULT hr = CreateRenderTarget(); 		if (FAILED(hr)) 		{ 			m_IsDrawing = false; 			return false; 		}  		// Recreate target bitmap 		Resize(m_W, m_H); 	}  	m_Target->BeginDraw(); 	m_IsDrawing = true; 	return true; }  void Canvas::EndDraw() { 	HRESULT hr = m_Target->EndDraw(); 	if (FAILED(hr)) 	{ 		m_Target.Reset(); 	}  	m_IsDrawing = false; }  HDC Canvas::GetDC() { 	if (m_IsDrawing) 	{ 		m_EnableDrawAfterGdi = true; 		m_IsDrawing = false; 		EndDraw(); 	}  	HDC hdc; 	HRESULT hr = m_BackBuffer->GetDC(FALSE, &hdc); 	if (FAILED(hr)) return nullptr;  	return hdc; }  void Canvas::ReleaseDC() { 	m_BackBuffer->ReleaseDC(nullptr);  	if (m_EnableDrawAfterGdi) 	{ 		m_EnableDrawAfterGdi = false; 		m_IsDrawing = true; 		BeginDraw(); 	} }  bool Canvas::IsTransparentPixel(int x, int y) { 	if (!(x >= 0 && y >= 0 && x < m_W && y < m_H)) return false;  	auto pixel = GetPixel(GetDC(), x, y); 	ReleaseDC();  	return (pixel & 0xFF000000) == 0; }  void Canvas::GetTransform(D2D1_MATRIX_3X2_F* matrix) { 	if (m_Target) 	{ 		m_Target->GetTransform(matrix); 	} }  void Canvas::SetTransform(const D2D1_MATRIX_3X2_F& matrix) { 	m_Target->SetTransform(matrix);  	m_CanUseAxisAlignClip = 		(matrix.m11 ==  1.0f && matrix.m12 ==  0.0f && matrix.m21 ==  0.0f && matrix.m22 ==  1.0f) ||	// Angle: 0 		(matrix.m11 ==  0.0f && matrix.m12 ==  1.0f && matrix.m21 == -1.0f && matrix.m22 ==  0.0f) ||	// Angle: 90 		(matrix.m11 == -1.0f && matrix.m12 ==  0.0f && matrix.m21 ==  0.0f && matrix.m22 == -1.0f) ||	// Angle: 180 		(matrix.m11 ==  0.0f && matrix.m12 == -1.0f && matrix.m21 ==  1.0f && matrix.m22 ==  0.0f);		// Angle: 270 }  void Canvas::ResetTransform() { 	m_Target->SetTransform(D2D1::Matrix3x2F::Identity()); }  bool Canvas::SetTarget(RenderTexture* texture) { 	auto bitmap = texture->GetBitmap(); 	if (bitmap->m_Segments.size() == 0) return false;  	auto image = bitmap->m_Segments[0].GetBitmap(); 	m_Target->SetTarget(image); 	return true; }  void Canvas::ResetTarget() { 	m_Target->SetTarget(m_TargetBitmap.Get()); } void Canvas::SetAntiAliasing(bool enable) { 	m_Target->SetAntialiasMode(enable ? D2D1_ANTIALIAS_MODE_PER_PRIMITIVE : D2D1_ANTIALIAS_MODE_ALIASED); }  void Canvas::SetTextAntiAliasing(bool enable) { 	// TODO: Add support for D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE? 	m_TextAntiAliasing = enable; 	m_Target->SetTextAntialiasMode(enable ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : D2D1_TEXT_ANTIALIAS_MODE_ALIASED); }  void Canvas::Clear(const D2D1_COLOR_F& color) { 	if (!m_Target) return;  	m_Target->Clear(color); } void Canvas::DrawTextW(const std::wstring& srcStr, const TextFormat& format, const D2D1_RECT_F& rect, 	const D2D1_COLOR_F& color, bool applyInlineFormatting) { 	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> solidBrush; 	HRESULT hr = m_Target->CreateSolidColorBrush(color, solidBrush.GetAddressOf()); 	if (FAILED(hr)) return;  	TextFormatD2D& formatD2D = (TextFormatD2D&)format;  	static std::wstring str; 	str = srcStr; 	formatD2D.ApplyInlineCase(str);  	if (!formatD2D.CreateLayout( 		m_Target.Get(), 		str, 		rect.right - rect.left, 		rect.bottom - rect.top, 		!m_AccurateText && m_TextAntiAliasing)) return;  	D2D1_POINT_2F drawPosition; 	drawPosition.x = [&]() 	{ 		if (!m_AccurateText) 		{ 			const float xOffset = formatD2D.m_TextFormat->GetFontSize() / 6.0f; 			switch (formatD2D.GetHorizontalAlignment()) 			{ 			case HorizontalAlignment::Left: return rect.left + xOffset; 			case HorizontalAlignment::Right: return rect.left - xOffset; 			} 		}  		return rect.left; 	} ();  	drawPosition.y = [&]() 	{ 		// GDI+ compatibility. 		float yPos = rect.top - formatD2D.m_LineGap; 		switch (formatD2D.GetVerticalAlignment()) 		{ 		case VerticalAlignment::Bottom: yPos -= formatD2D.m_ExtraHeight; break; 		case VerticalAlignment::Center: yPos -= formatD2D.m_ExtraHeight / 2.0f; break; 		}  		return yPos; 	} ();  	if (formatD2D.m_Trimming) 	{ 		D2D1_RECT_F clipRect = rect;  		if (m_CanUseAxisAlignClip) 		{ 			m_Target->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED); 		} 		else 		{ 			const D2D1_LAYER_PARAMETERS1 layerParams = 				D2D1::LayerParameters1(clipRect, nullptr, D2D1_ANTIALIAS_MODE_ALIASED); 			m_Target->PushLayer(layerParams, nullptr); 		} 	}  	// When different "effects" are used with inline coloring options, we need to 	// remove the previous inline coloring, then reapply them (if needed) - instead 	// of destroying/recreating the text layout. 	UINT32 strLen = (UINT32)str.length(); 	formatD2D.ResetInlineColoring(solidBrush.Get(), strLen); 	if (applyInlineFormatting) 	{ 		formatD2D.ApplyInlineColoring(m_Target.Get(), &drawPosition);  		// Draw any 'shadow' effects 		formatD2D.ApplyInlineShadow(m_Target.Get(), solidBrush.Get(), strLen, drawPosition); 	}  	m_Target->DrawTextLayout(drawPosition, formatD2D.m_TextLayout.Get(), solidBrush.Get());  	if (applyInlineFormatting) 	{ 		// Inline gradients require the drawing position, so in case that position 		// changes, we need a way to reset it after drawing time so on the next 		// iteration it will know the correct position. 		formatD2D.ResetGradientPosition(&drawPosition); 	}  	if (formatD2D.m_Trimming) 	{ 		if (m_CanUseAxisAlignClip) 		{ 			m_Target->PopAxisAlignedClip(); 		} 		else 		{ 			m_Target->PopLayer(); 		} 	} } bool Canvas::MeasureTextW(const std::wstring& str, const TextFormat& format, D2D1_SIZE_F& size) { 	TextFormatD2D& formatD2D = (TextFormatD2D&)format;  	static std::wstring formatStr; 	formatStr = str; 	formatD2D.ApplyInlineCase(formatStr);  	const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(formatStr, !m_AccurateText); 	size.width = metrics.width; 	size.height = metrics.height; 	return true; }  bool Canvas::MeasureTextLinesW(const std::wstring& str, const TextFormat& format, D2D1_SIZE_F& size, UINT32& lines) { 	TextFormatD2D& formatD2D = (TextFormatD2D&)format; 	formatD2D.m_TextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);  	static std::wstring formatStr; 	formatStr = str; 	formatD2D.ApplyInlineCase(formatStr);  	const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(formatStr, !m_AccurateText, size.width); 	size.width = metrics.width; 	size.height = metrics.height; 	lines = metrics.lineCount;  	if (size.height > 0.0f) 	{ 		// GDI+ draws multi-line text even though the last line may be clipped slightly at the 		// bottom. This is a workaround to emulate that behaviour. 		size.height += 1.0f; 	} 	else 	{ 		// GDI+ compatibility: Zero height text has no visible lines. 		lines = 0U; 	} 	return true; }  void Canvas::DrawBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect) { 	auto& segments = bitmap->m_Segments; 	for (auto seg : segments) 	{ 		const auto rSeg = seg.GetRect(); 		D2D1_RECT_F rSrc = (rSeg.left < rSeg.right && rSeg.top < rSeg.bottom) ? 			D2D1::RectF( 				max(rSeg.left, srcRect.left), 				max(rSeg.top, srcRect.top), 				min(rSeg.right + rSeg.left, srcRect.right), 				min(rSeg.bottom + rSeg.top, srcRect.bottom)) : 			D2D1::RectF(); 		if (rSrc.left == rSrc.right || rSrc.top == rSrc.bottom) continue;  		const D2D1_RECT_F rDst = D2D1::RectF( 			(rSrc.left   - srcRect.left) / (srcRect.right  - srcRect.left) * (dstRect.right  - dstRect.left) + dstRect.left, 			(rSrc.top    - srcRect.top)  / (srcRect.bottom - srcRect.top)  * (dstRect.bottom - dstRect.top)  + dstRect.top, 			(rSrc.right  - srcRect.left) / (srcRect.right  - srcRect.left) * (dstRect.right  - dstRect.left) + dstRect.left, 			(rSrc.bottom - srcRect.top)  / (srcRect.bottom - srcRect.top)  * (dstRect.bottom - dstRect.top)  + dstRect.top);  		while (rSrc.top >= m_MaxBitmapSize) 		{ 			rSrc.bottom -= m_MaxBitmapSize; 			rSrc.top -= m_MaxBitmapSize; 		}  		while (rSrc.left >= m_MaxBitmapSize) 		{ 			rSrc.right -= m_MaxBitmapSize; 			rSrc.left -= m_MaxBitmapSize; 		}  		m_Target->DrawBitmap(seg.GetBitmap(), rDst, 1.0f, D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC, &rSrc); 	} }  void Canvas::DrawTiledBitmap(const D2DBitmap* bitmap, const D2D1_RECT_F& dstRect, const D2D1_RECT_F& srcRect) { 	const FLOAT width = (FLOAT)bitmap->m_Width; 	const FLOAT height = (FLOAT)bitmap->m_Height;  	FLOAT x = dstRect.left; 	FLOAT y = dstRect.top;  	while (y < dstRect.bottom) 	{ 		const FLOAT w = (dstRect.right - x) > width ? width : (dstRect.right - x); 		const FLOAT h = (dstRect.bottom - y) > height ? height : (dstRect.bottom - y);  		const auto dst = D2D1::RectF(x, y, x + w, y + h); 		const auto src = D2D1::RectF(0.0f, 0.0f, w, h); 		DrawBitmap(bitmap, dst, src);  		x += width; 		if (x >= dstRect.right && y < dstRect.bottom) 		{ 			x = dstRect.left; 			y += height; 		} 	} }  void Canvas::DrawMaskedBitmap(const D2DBitmap* bitmap, const D2DBitmap* maskBitmap, const D2D1_RECT_F& dstRect, 	const D2D1_RECT_F& srcRect, const D2D1_RECT_F& srcRect2) { 	if (!bitmap || !maskBitmap) return;  	// Create bitmap brush from original |bitmap|. 	Microsoft::WRL::ComPtr<ID2D1BitmapBrush1> brush; 	D2D1_BITMAP_BRUSH_PROPERTIES1 propertiesXClampYClamp = D2D1::BitmapBrushProperties1( 		D2D1_EXTEND_MODE_CLAMP, 		D2D1_EXTEND_MODE_CLAMP, 		D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC);  	const FLOAT width = (FLOAT)bitmap->m_Width; 	const FLOAT height = (FLOAT)bitmap->m_Height;  	auto getRectSubRegion = [&width, &height](const D2D1_RECT_F& r1, const D2D1_RECT_F& r2) -> D2D1_RECT_F 	{ 		return D2D1::RectF( 			r1.left / width * r2.right + r2.left, 			r1.top / height * r2.bottom + r2.top, 			(r1.right - r1.left) / width * r2.right, 			(r1.bottom - r1.top) / height * r2.bottom); 	};  	for (auto bseg : bitmap->m_Segments) 	{ 		const auto rSeg = bseg.GetRect(); 		const auto rDst = getRectSubRegion(rSeg, dstRect); 		const auto rSrc = getRectSubRegion(rSeg, srcRect);  		FLOAT s2Width = srcRect2.right - srcRect2.left; 		FLOAT s2Height = srcRect2.bottom - srcRect2.top;  		// "Move" and "scale" the |bitmap| to match the destination. 		D2D1_MATRIX_3X2_F translateMask = D2D1::Matrix3x2F::Translation(-srcRect2.left, -srcRect2.top); 		D2D1_MATRIX_3X2_F translate = D2D1::Matrix3x2F::Translation(rDst.left, rDst.top); 		D2D1_MATRIX_3X2_F scale = D2D1::Matrix3x2F::Scale( 			D2D1::SizeF((rDst.right - rDst.left) / s2Width, (rDst.bottom - rDst.top) / s2Height)); 		D2D1_BRUSH_PROPERTIES brushProps = D2D1::BrushProperties(1.0f, translateMask * scale * translate);  		HRESULT hr = m_Target->CreateBitmapBrush( 			bseg.GetBitmap(), 			propertiesXClampYClamp, 			brushProps, 			brush.ReleaseAndGetAddressOf()); 		if (FAILED(hr)) return;  		const auto aaMode = m_Target->GetAntialiasMode(); 		m_Target->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); // required  		for (auto mseg : maskBitmap->m_Segments) 		{ 			const auto rmSeg = mseg.GetRect(); 			const auto rmDst = getRectSubRegion(rmSeg, dstRect); 			const auto rmSrc = getRectSubRegion(rmSeg, srcRect);  			// If no overlap, don't draw 			if ((rmDst.left < (rDst.left + rDst.right) && 				(rmDst.right + rmDst.left) > rDst.left && 				rmDst.top > (rmDst.top + rmDst.bottom) && 				(rmDst.top + rmDst.bottom) < rmDst.top)) continue;  			m_Target->FillOpacityMask( 				mseg.GetBitmap(), 				brush.Get(), 				D2D1_OPACITY_MASK_CONTENT_GRAPHICS, 				&rDst, 				&rSrc); 		}  		m_Target->SetAntialiasMode(aaMode); 	} }  void Canvas::FillRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color) { 	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> solidBrush; 	HRESULT hr = m_Target->CreateSolidColorBrush(color, solidBrush.GetAddressOf()); 	if (SUCCEEDED(hr)) 	{ 		m_Target->FillRectangle(rect, solidBrush.Get()); 	} }  void Canvas::FillGradientRectangle(const D2D1_RECT_F& rect, const D2D1_COLOR_F& color1, const D2D1_COLOR_F& color2, const FLOAT& angle) { 	// D2D requires 2 points to draw the gradient along where GDI+ just requires a rectangle. To 	// mimic GDI+ for SolidColor2, we have to find and swap the starting and ending points of where 	// the gradient touches edge of the bounding rectangle. Normally we would offset the ending 	// point by 180, but we do this on starting point for SolidColor2. This differs from the other 	// D2D gradient options below: 	//  Gfx::TextInlineFormat_GradientColor::BuildGradientBrushes 	//  Gfx::Shape::CreateLinearGradient 	D2D1_POINT_2F start = Util::FindEdgePoint(angle + 180.0f, rect.left, rect.top, rect.right, rect.bottom); 	D2D1_POINT_2F end = Util::FindEdgePoint(angle, rect.left, rect.top, rect.right, rect.bottom);  	Microsoft::WRL::ComPtr<ID2D1GradientStopCollection> pGradientStops;  	D2D1_GRADIENT_STOP gradientStops[2]; 	gradientStops[0].color = color1; 	gradientStops[0].position = 0.0f; 	gradientStops[1].color = color2; 	gradientStops[1].position = 1.0f;  	HRESULT hr = m_Target->CreateGradientStopCollection( 		gradientStops, 		2U, 		D2D1_GAMMA_2_2, 		D2D1_EXTEND_MODE_CLAMP, 		pGradientStops.GetAddressOf()); 	if (FAILED(hr)) return;  	Microsoft::WRL::ComPtr<ID2D1LinearGradientBrush> brush; 	hr = m_Target->CreateLinearGradientBrush( 		D2D1::LinearGradientBrushProperties(start, end), 		pGradientStops.Get(), 		brush.GetAddressOf()); 	if (FAILED(hr)) return;  	m_Target->FillRectangle(rect, brush.Get()); }  void Canvas::DrawLine(const D2D1_COLOR_F& color, FLOAT x1, FLOAT y1, FLOAT x2, FLOAT y2, FLOAT strokeWidth) { 	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> solidBrush; 	HRESULT hr = m_Target->CreateSolidColorBrush(color, solidBrush.GetAddressOf()); 	if (FAILED(hr)) return;  	m_Target->DrawLine(D2D1::Point2F(x1, y1), D2D1::Point2F(x2, y2), solidBrush.Get(), strokeWidth); }  void Canvas::DrawGeometry(Shape& shape, int xPos, int yPos) { 	D2D1_MATRIX_3X2_F worldTransform; 	m_Target->GetTransform(&worldTransform); 	m_Target->SetTransform( 		shape.GetShapeMatrix() * 		D2D1::Matrix3x2F::Translation((FLOAT)xPos, (FLOAT)yPos) * 		worldTransform);  	auto fill = shape.GetFillBrush(m_Target.Get()); 	if (fill) 	{ 		m_Target->FillGeometry(shape.m_Shape.Get(), fill.Get()); 	}  	auto stroke = shape.GetStrokeFillBrush(m_Target.Get()); 	if (stroke) 	{ 		m_Target->DrawGeometry( 			shape.m_Shape.Get(), 			stroke.Get(), 			shape.m_StrokeWidth, 			shape.m_StrokeStyle.Get()); 	}  	m_Target->SetTransform(worldTransform); }  HRESULT Canvas::CreateRenderTarget() { 	HRESULT hr = E_FAIL; 	if (c_D2DDevice) 	{ 		c_D2DDevice->ClearResources();  		hr = c_D2DDevice->CreateDeviceContext( 			D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, 			m_Target.ReleaseAndGetAddressOf()); 		if (FAILED(hr)) 		{ 			hr = c_D2DDevice->CreateDeviceContext( 				D2D1_DEVICE_CONTEXT_OPTIONS_NONE, 				m_Target.ReleaseAndGetAddressOf()); 		} 	}  	// Hardware accelerated targets have a hard limit to the size of bitmaps they can support. 	// The size will depend on the D3D feature level of the driver used. The WARP software 	// renderer has a limit of 16MP (16*1024*1024 = 16777216).  	// https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-devices-downlevel-intro#overview-for-each-feature-level 	// Max Texture Dimension 	// D3D_FEATURE_LEVEL_11_1 = 16348 	// D3D_FEATURE_LEVEL_11_0 = 16348 	// D3D_FEATURE_LEVEL_10_1 = 8192 	// D3D_FEATURE_LEVEL_10_0 = 8192 	// D3D_FEATURE_LEVEL_9_3  = 4096 	// D3D_FEATURE_LEVEL_9_2  = 2048 	// D3D_FEATURE_LEVEL_9_1  = 2048  	if (SUCCEEDED(hr)) 	{ 		m_MaxBitmapSize = m_Target->GetMaximumBitmapSize(); 	}  	return hr; }  bool Canvas::CreateTargetBitmap(UINT32 width, UINT32 height) { 	HRESULT hr = m_SwapChain->GetBuffer(0U, IID_PPV_ARGS(m_BackBuffer.GetAddressOf())); 	if (FAILED(hr)) return LogComError(hr);  	D2D1_BITMAP_PROPERTIES1 bProps = D2D1::BitmapProperties1( 		D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, 		D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED));  	hr = m_Target->CreateBitmapFromDxgiSurface( 		m_BackBuffer.Get(), 		&bProps, 		m_TargetBitmap.GetAddressOf()); 	if (FAILED(hr)) return LogComError(hr);  	m_Target->SetTarget(m_TargetBitmap.Get()); 	return true; }  }  // namespace Gfx 

То что надо:

Microsoft::WRL::ComPtr<ID3D11Device> Canvas::c_D3DDevice; Microsoft::WRL::ComPtr<ID3D11DeviceContext> Canvas::c_D3DContext; Microsoft::WRL::ComPtr<ID2D1Device> Canvas::c_D2DDevice; Microsoft::WRL::ComPtr<IDXGIDevice1> Canvas::c_DxgiDevice; Microsoft::WRL::ComPtr<ID2D1Factory1> Canvas::c_D2DFactory; Microsoft::WRL::ComPtr<IDWriteFactory1> Canvas::c_DWFactory; Microsoft::WRL::ComPtr<IWICImagingFactory> Canvas::c_WICFactory; . . . Ещё всякое 

Добавим структуру Context в Common/Gfx/Canvas.h:

Context

. . . class Canvas { public:  	struct Context 	{ 		Microsoft::WRL::ComPtr<ID3D11Device> D3DDevice; 		Microsoft::WRL::ComPtr<ID3D11DeviceContext> D3DContext; 		Microsoft::WRL::ComPtr<ID2D1Device> D2DDevice; 		Microsoft::WRL::ComPtr<IDXGIDevice1> DxgiDevice; 		Microsoft::WRL::ComPtr<ID2D1Factory1> D2DFactory; 		Microsoft::WRL::ComPtr<IDWriteFactory1> DWFactory; 		Microsoft::WRL::ComPtr<IWICImagingFactory> WICFactory; 		Microsoft::WRL::ComPtr<ID2D1DeviceContext> Target; 		Microsoft::WRL::ComPtr<IDXGISwapChain1> SwapChain; 		Microsoft::WRL::ComPtr<IDXGISurface1> BackBuffer; 		Microsoft::WRL::ComPtr<ID2D1Bitmap1> TargetBitmap; 		int W; 		int H; 		UINT32 MaxBitmapSize; 		bool IsDrawing; 		bool EnableDrawAfterGdi; 		bool AccurateText; 		bool TextAntiAliasing; 		bool CanUseAxisAlignClip; 	};  	Context GetContext(); 	. 	. 	. }; 

Common/Canvas.cpp

. . . Canvas::Context Canvas::GetContext() { 	Context context; 	context.D2DDevice = c_D2DDevice; 	context.D2DFactory = c_D2DFactory; 	context.D3DContext = c_D3DContext; 	context.D3DDevice = c_D3DDevice; 	context.DWFactory = c_DWFactory; 	context.WICFactory = c_WICFactory; 	context.DxgiDevice = c_DxgiDevice; 	context.AccurateText = m_AccurateText; 	context.BackBuffer = m_BackBuffer; 	context.CanUseAxisAlignClip = m_CanUseAxisAlignClip; 	context.EnableDrawAfterGdi = m_EnableDrawAfterGdi; 	context.H = m_H; 	context.IsDrawing = m_IsDrawing; 	context.MaxBitmapSize = m_MaxBitmapSize; 	context.SwapChain = m_SwapChain; 	context.Target = m_Target; 	context.TargetBitmap = m_TargetBitmap; 	context.TextAntiAliasing = m_TextAntiAliasing; 	context.W = m_W; 	return context; } . . . 

Создадим новый meter — Canvas:

Library/MeterCanvas.h

#ifndef __METERCANVAS_H__ #define __METERCANVAS_H__  #include "Meter.h"  class MeterCanvas : public Meter { public: 	MeterCanvas(Skin* skin, const WCHAR* name);  	UINT GetTypeID() override { return TypeID<MeterCanvas>(); }  	virtual bool Update(); 	virtual bool Draw(Gfx::Canvas& canvas);  	bool HitTest(int x, int y);  	~MeterCanvas(); }; #endif 

Library/MeterCanvas.cpp

#include "StdAfx.h" #include "MeterCanvas.h" #include "Logger.h" #include "windows.h" #include <iostream> #include "../Common/Gfx/Canvas.h" #include "../Common/Gfx/Util/D2DUtil.h" MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name) { 	Meter::Initialize(); }  bool MeterCanvas::Update() { 	return Meter::Update(); }  bool MeterCanvas::Draw(Gfx::Canvas& canvas) { 	return Meter::Draw(canvas); } bool MeterCanvas::HitTest(int x, int y) { 	return Meter::HitTest(x, y); }  MeterCanvas::~MeterCanvas() { 	 }  

Добавим возможность использования meter’а:

Library/Meter.cpp

#include "MeterCanvas.h" . . . Meter* Meter::Create(const WCHAR* meter, Skin* skin, const WCHAR* name) { 	if (_wcsicmp(L"STRING", meter) == 0) 	{ 		return new MeterString(skin, name); 	} 	else if (_wcsicmp(L"IMAGE", meter) == 0) 	{ 		return new MeterImage(skin, name); 	} 	else if (_wcsicmp(L"HISTOGRAM", meter) == 0) 	{ 		return new MeterHistogram(skin, name); 	} 	else if (_wcsicmp(L"BAR", meter) == 0) 	{ 		return new MeterBar(skin, name); 	} 	else if (_wcsicmp(L"BITMAP", meter) == 0) 	{ 		return new MeterBitmap(skin, name); 	} 	else if (_wcsicmp(L"LINE", meter) == 0) 	{ 		return new MeterLine(skin, name); 	} 	else if (_wcsicmp(L"ROUNDLINE", meter) == 0) 	{ 		return new MeterRoundLine(skin, name); 	} 	else if (_wcsicmp(L"ROTATOR", meter) == 0) 	{ 		return new MeterRotator(skin, name); 	} 	else if (_wcsicmp(L"BUTTON", meter) == 0) 	{ 		return new MeterButton(skin, name); 	} 	else if (_wcsicmp(L"SHAPE", meter) == 0) 	{ 		return new MeterShape(skin, name); 	} 	else if (_wcsicmp(L"CANVAS", meter) == 0) 	{ 		return new MeterCanvas(skin, name); 	}  	LogErrorF(skin, L"Meter=%s is not valid in [%s]", meter, name);  	return nullptr; } 

Попробуем что-то нарисовать:

Meter.ini

[Rainmeter] Update=1000  [Text] Meter=CANVAS W=100 H=100 

Library/MeterCanvas.cpp

bool MeterCanvas::Draw(Gfx::Canvas& canvas) { 	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush; 	auto target = canvas.GetContext().Target; 	target->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red), brush.GetAddressOf()); 	target->FillRectangle(D2D1::RectF(100, 100), brush.Get()); 	return Meter::Draw(canvas); } 

Работает:

image

Теперь напишем dll для отрисовки:

Draw.h

#include <d2d1_1.h> #include <wrl\client.h> #include "Context.h" #pragma once #ifdef LIBRARY_EXPORTS #define EXPORT_PLUGIN #else #define EXPORT_PLUGIN __declspec(dllexport) #endif extern "C" EXPORT_PLUGIN void Draw(Context context); 

Context.h

#pragma once #include <string> #include <d2d1_1.h> #include <dwrite_1.h> #include <wincodec.h> #include <wrl/client.h> #include <d3d11.h> #include <DXGI1_2.h> struct Context { 	Microsoft::WRL::ComPtr<ID3D11Device> D3DDevice; 	Microsoft::WRL::ComPtr<ID3D11DeviceContext> D3DContext; 	Microsoft::WRL::ComPtr<ID2D1Device> D2DDevice; 	Microsoft::WRL::ComPtr<IDXGIDevice1> DxgiDevice; 	Microsoft::WRL::ComPtr<ID2D1Factory1> D2DFactory; 	Microsoft::WRL::ComPtr<IDWriteFactory1> DWFactory; 	Microsoft::WRL::ComPtr<IWICImagingFactory> WICFactory; 	Microsoft::WRL::ComPtr<ID2D1DeviceContext> Target; 	Microsoft::WRL::ComPtr<IDXGISwapChain1> SwapChain; 	Microsoft::WRL::ComPtr<IDXGISurface1> BackBuffer; 	Microsoft::WRL::ComPtr<ID2D1Bitmap1> TargetBitmap; 	int W; 	int H; 	UINT32 MaxBitmapSize; 	bool IsDrawing; 	bool EnableDrawAfterGdi; 	bool AccurateText; 	bool TextAntiAliasing; 	bool CanUseAxisAlignClip; }; 

Draw.cpp

#include "pch.h" #include "Draw.h"  #pragma comment(lib,"d2d1.lib") EXPORT_PLUGIN void Draw(Context context) { 	Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush; 	auto target = context.Target; 	target->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red), brush.GetAddressOf()); 	target->FillRectangle(D2D1::RectF(100, 100), brush.Get()); } 

Компилируем Draw.dll:

Library/MeterCanvas.cpp

HMODULE hlib; void (*draw)(Gfx::Canvas::Context context); MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name) { 	hlib = LoadLibrary(L"Draw.dll"); 	(FARPROC&)draw = GetProcAddress(hlib, "Draw"); 	Meter::Initialize(); } bool MeterCanvas::Draw(Gfx::Canvas& canvas) { 	if (draw != nullptr) 		draw(canvas.GetContext()); 	return Meter::Draw(canvas); } MeterCanvas::~MeterCanvas() { 	FreeLibrary(hlib); } 

Тоже работает:

image

Допишем код, для выбора dll’ки пользователем:

std::wstring dll; . . . MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name) { 	dll = skin->GetParser().GetValue(name, L"Dll", L"");  	hlib = LoadLibrary(dll.c_str());  	(FARPROC&)draw = GetProcAddress(hlib, "Draw");  	Meter::Initialize(); } 

Meter.ini

[Rainmeter] Update=1000  [Text] Meter=CANVAS Dll=Draw.dll W=100 H=100 

Почему бы не сделать отдельный bang?
Сделаем.

Добавим Action в список bang’ов.

Library/CommandHandler.h:

enum class Bang { 	Action, 	. 	. 	. } 

Library/CommandHandler.cpp

const BangInfo s_Bangs[] = { 	{Bang::Action, L"Action", 1}, 	. 	. 	. } 

Library/Skin.cpp:

void Skin::DoBang(Bang bang, const std::vector<std::wstring>& args) { 	switch (bang) 	{ 	case Bang::Action: 		GetMeters()[0]->Action(args[0]); 		break; 	. 	. 	. } 

Library/Meter.h:

class __declspec(novtable) Meter : public Section { public: 	virtual void Action(std::wstring arg) {}; 	. 	. 	. } 

Library/MeterCanvas.h:

class MeterCanvas : public Meter { public: 	virtual void Action(std::wstring arg) {}; 	. 	. 	. } 

Meter.ini:

[Rainmeter] Update=1000  [Text] Meter=CANVAS Dll=Draw.dll W=100 H=100 LeftMouseDownAction=!Action Test 

Обработка

Library/MeterCanvas.cpp:

. . . void MeterCanvas::Action(std::wstring arg) { 	LogDebug(arg.c_str()); } 

image

Из dll

Library/MeterCanvas.cpp

void (*action)(std::wstring arg); . . . MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name) { 	hlib = LoadLibrary(L"Draw.dll"); 	(FARPROC&)draw = GetProcAddress(hlib, "Draw"); 	(FARPROC&)action = GetProcAddress(hlib, "Action"); 	Meter::Initialize(); } void MeterCanvas::Action(std::wstring arg) { 	if (action != nullptr) 		action(arg); } 

Draw.h

#include <d2d1_1.h> #include <wrl\client.h> #include <math.h> #include "Context.h" #pragma once #ifdef LIBRARY_EXPORTS #define EXPORT_PLUGIN #else #define EXPORT_PLUGIN __declspec(dllexport) #endif extern "C" EXPORT_PLUGIN void Draw(Context context);  extern "C" EXPORT_PLUGIN void Action(std::wstring arg); 

Также добавим действие при загрузке:

Скрытый текст

Draw.h

#include <d2d1_1.h> #include <wrl\client.h> #include <math.h> #include "Context.h" #pragma once #ifdef LIBRARY_EXPORTS #define EXPORT_PLUGIN #else #define EXPORT_PLUGIN __declspec(dllexport) #endif extern "C" EXPORT_PLUGIN void Init(Context context);  extern "C" EXPORT_PLUGIN void Draw(Context context);  extern "C" EXPORT_PLUGIN void Action(std::wstring arg); 

Library/MeterCanvas.cpp

void (*init)(Gfx::Canvas::Context context); . . . MeterCanvas::MeterCanvas(Skin* skin, const WCHAR* name) : Meter(skin, name) { 	dll = skin->GetParser().GetValue(name, L"Dll", L"");  	hlib = LoadLibrary(dll.c_str());  	(FARPROC&)draw = GetProcAddress(hlib, "Draw");  	(FARPROC&)action = GetProcAddress(hlib, "Action");  	(FARPROC&)init = GetProcAddress(hlib, "Init");                           	init(skin->GetCanvas().GetContext());  	Meter::Initialize(); } 

Почти безграничная власть!

Но чего-то не хватает…

Где же обработка нажатий на клаву?

А вот она:

Но оно не работает.
Не беда.

Заходим в rainmeter-master\x32-Debug\Plugins
Нам нужны еще файлы .pdb и .ilk

Качаем.

Компилируем, кидаем к плагинам.

Вот и всё.
Вы можете вообще всё.

В качестве бонуса — пример:

Видео

Исходники

ссылка на оригинал статьи https://habr.com/ru/post/512510/