Доброго времени суток. Всегда хотел свободы в 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
/* 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:
.
.
.
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();
.
.
.
};
.
.
.
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:
#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
#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'а:
#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;
}
Попробуем что-то нарисовать:
[Rainmeter]
Update=1000
[Text]
Meter=CANVAS
W=100
H=100
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);
}
Работает:
Теперь напишем dll для отрисовки:
#include <d2d1_1.h>
#include <wrlclient.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);
#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;
};
#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:
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);
}
Тоже работает:
Допишем код, для выбора 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
.
.
.
void MeterCanvas::Action(std::wstring arg)
{
LogDebug(arg.c_str());
}
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 <wrlclient.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);
Также добавим действие при загрузке:
#include <d2d1_1.h>
#include <wrlclient.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-masterx32-DebugPlugins
Нам нужны еще файлы .pdb и .ilk
Качаем.
Компилируем, кидаем к плагинам.
Вот и всё.
Вы можете вообще всё.
В качестве бонуса — пример:
Автор: ATR19