317 lines
9.5 KiB
C++
317 lines
9.5 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved
|
|
|
|
#include <math.h>
|
|
#include "Shape.h"
|
|
|
|
// Shape Constructors
|
|
CShape::CShape() :
|
|
_d2dDriver(nullptr),
|
|
_pRenderTarget(nullptr),
|
|
_pPathGeometry(nullptr),
|
|
_objectId(0),
|
|
_isDraggable(true),
|
|
_isSelected(false),
|
|
_objectType(OBJECT_TYPE_REGULAR),
|
|
_text(nullptr)
|
|
{
|
|
}
|
|
|
|
CShape::CShape(CD2DDriver *d2dDriver) :
|
|
_d2dDriver(d2dDriver),
|
|
_pRenderTarget(nullptr),
|
|
_pPathGeometry(nullptr),
|
|
_objectId(0),
|
|
_isDraggable(true),
|
|
_isSelected(false),
|
|
_objectType(OBJECT_TYPE_REGULAR),
|
|
_text(nullptr)
|
|
{
|
|
InitializeDriver(d2dDriver);
|
|
}
|
|
|
|
// Shape Descructor
|
|
CShape::~CShape()
|
|
{
|
|
if (_pPathGeometry != nullptr)
|
|
{
|
|
_pPathGeometry->Release();
|
|
_pPathGeometry = nullptr;
|
|
}
|
|
}
|
|
|
|
// initialize with d2dDriver
|
|
void CShape::InitializeDriver(CD2DDriver *d2dDriver)
|
|
{
|
|
_d2dDriver = d2dDriver;
|
|
// Get the render target for drawing to
|
|
_pRenderTarget = _d2dDriver->GetRenderTarget();
|
|
}
|
|
|
|
// Initialize shape object
|
|
void CShape::InitializeObject(_In_reads_(numPoints) const POINT *points, _In_ long numPoints,
|
|
_In_ bool isDraggable, _In_ int objectId, _In_ DrawingColor color,
|
|
_In_ DrawingColor onSelectColor, _In_ OBJECT_TYPE objectType)
|
|
{
|
|
_polygonRelative.numPoints = numPoints;
|
|
_objectId = objectId;
|
|
_isDraggable = isDraggable;
|
|
_color = color;
|
|
_onSelectColor = onSelectColor;
|
|
_objectType = objectType;
|
|
|
|
if (numPoints == 2)
|
|
{
|
|
_polygonRelative.isRect = true;
|
|
_polygonRelative.rcBound.left = points[0].x;
|
|
_polygonRelative.rcBound.top = points[0].y;
|
|
_polygonRelative.rcBound.right = points[1].x;
|
|
_polygonRelative.rcBound.bottom = points[1].y;
|
|
|
|
// Initialize points
|
|
_polygonRelative.points[0] = points[0];
|
|
_polygonRelative.points[1].x = points[1].x;
|
|
_polygonRelative.points[1].y = points[0].y;
|
|
_polygonRelative.points[2] = points[1];
|
|
_polygonRelative.points[3].x = points[0].x;
|
|
_polygonRelative.points[3].y = points[1].y;
|
|
_polygonRelative.points[4] = points[0];
|
|
_polygonRelative.numPoints = 5;
|
|
}
|
|
else
|
|
{
|
|
_polygonRelative.isRect = false;
|
|
|
|
// calculate width and height
|
|
if (numPoints > 0)
|
|
{
|
|
_polygonRelative.points[0] = points[0];
|
|
_polygonRelative.rcBound.left = _polygonRelative.rcBound.right = points[0].x;
|
|
_polygonRelative.rcBound.top = _polygonRelative.rcBound.bottom = points[0].y;
|
|
|
|
// Copy polygon points to _polygonRelative object and calculate polygon bounding box
|
|
for (int i = 1; i < numPoints; i++)
|
|
{
|
|
_polygonRelative.points[i] = points[i];
|
|
_polygonRelative.rcBound.left = min(_polygonRelative.rcBound.left, points[i].x);
|
|
_polygonRelative.rcBound.right = max(_polygonRelative.rcBound.right, points[i].x);
|
|
_polygonRelative.rcBound.top = min(_polygonRelative.rcBound.top, points[i].y);
|
|
_polygonRelative.rcBound.bottom = max(_polygonRelative.rcBound.bottom, points[i].y);
|
|
}
|
|
}
|
|
}
|
|
|
|
_polygonAbsolute = _polygonRelative;
|
|
}
|
|
|
|
// Sets the default position, dimensions and color for the drawing object
|
|
void CShape::ResetState(_In_ float startX, _In_ float startY,
|
|
_In_ int scaledWidth, _In_ int scaledHeight,
|
|
_In_ float scale)
|
|
{
|
|
// Set width and height of the client area
|
|
// must adjust for dpi aware
|
|
_width = scaledWidth;
|
|
_height = scaledHeight;
|
|
|
|
// Set coordinates used for rendering
|
|
_xR = startX;
|
|
_yR = startY;
|
|
|
|
// scaling factor
|
|
_scale = scale;
|
|
}
|
|
|
|
// Draw shape object
|
|
void CShape::Paint()
|
|
{
|
|
if (!(_pRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED))
|
|
{
|
|
// DimGray is selected if it was clicked previously and pointer is over it
|
|
bool chooseSelectBrush = _isSelected;
|
|
if (chooseSelectBrush)
|
|
{
|
|
chooseSelectBrush = InRegion(_pointerPosition.x, _pointerPosition.y);
|
|
}
|
|
ID2D1LinearGradientBrush *brush = _d2dDriver->GetBrush(chooseSelectBrush ? _onSelectColor : _color);
|
|
|
|
// Set positions of gradients based on the new coordinates of the objecs
|
|
brush->SetStartPoint(_UnscaledToClient(*reinterpret_cast<POINT *>(&_polygonRelative.rcBound)));
|
|
|
|
brush->SetEndPoint(_UnscaledToClient(*reinterpret_cast<POINT *>(&_polygonRelative.rcBound.right)));
|
|
|
|
// Create path to draw
|
|
if (_pPathGeometry != nullptr)
|
|
{
|
|
_pPathGeometry->Release();
|
|
}
|
|
_d2dDriver->CreatePathGeometry(&_pPathGeometry);
|
|
ID2D1GeometrySink *pSink;
|
|
_pPathGeometry->Open(&pSink);
|
|
|
|
if (!_polygonRelative.isRect)
|
|
{
|
|
pSink->BeginFigure(
|
|
_UnscaledToClient(_polygonRelative.points[0]),
|
|
D2D1_FIGURE_BEGIN_FILLED);
|
|
}
|
|
else
|
|
{
|
|
pSink->BeginFigure(
|
|
_UnscaledToClient(*reinterpret_cast<POINT *>(&_polygonRelative.rcBound)),
|
|
D2D1_FIGURE_BEGIN_FILLED);
|
|
}
|
|
|
|
// Draw polygon
|
|
for (int i = 1; i < _polygonRelative.numPoints; i++)
|
|
{
|
|
pSink->AddLine(_UnscaledToClient(_polygonRelative.points[i]));
|
|
}
|
|
|
|
pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
|
|
pSink->Close();
|
|
pSink->Release();
|
|
|
|
// Fill the geometry that was created
|
|
_pRenderTarget->FillGeometry(_pPathGeometry, brush);
|
|
|
|
// Draw text if there's any
|
|
if (_text != nullptr)
|
|
{
|
|
_pRenderTarget->DrawText(
|
|
_text,
|
|
_textLen,
|
|
_d2dDriver->GetTextFormat(),
|
|
_UnscaledToClient(_polygonRelative.rcBound),
|
|
_d2dDriver->GetBrush(Orange));
|
|
}
|
|
|
|
// Create absolute polygon
|
|
for (int i = 0; i < _polygonRelative.numPoints; i++)
|
|
{
|
|
_UnscaledToClient(_polygonRelative.points[i], &_polygonAbsolute.points[i]);
|
|
}
|
|
_UnscaledToClient(*reinterpret_cast<POINT *>(&_polygonRelative.rcBound), reinterpret_cast<POINT *>((&_polygonAbsolute.rcBound)));
|
|
_UnscaledToClient(*reinterpret_cast<POINT *>(&_polygonRelative.rcBound.right), reinterpret_cast<POINT *>((&_polygonAbsolute.rcBound.right)));
|
|
}
|
|
}
|
|
|
|
// Hit testing method handled with Direct2D
|
|
bool CShape::InRegion(_In_ long x, _In_ long y)
|
|
{
|
|
BOOL b = FALSE;
|
|
|
|
_pPathGeometry->FillContainsPoint(
|
|
D2D1::Point2F(static_cast<float>(x), static_cast<float>(y)),
|
|
D2D1::Matrix3x2F::Identity(),
|
|
&b
|
|
);
|
|
|
|
return !!b;
|
|
}
|
|
|
|
// Pointer event handler
|
|
void CShape::OnPointerEvent(_In_ POINT pt, _In_ EVENT_TYPE et)
|
|
{
|
|
switch (et)
|
|
{
|
|
case EVENT_TYPE_DOWN:
|
|
_isSelected = true;
|
|
break;
|
|
case EVENT_TYPE_MOVE:
|
|
if (_isDraggable)
|
|
{
|
|
_Translate(static_cast<float>(pt.x - _pointerPosition.x), static_cast<float>(pt.y - _pointerPosition.y));
|
|
}
|
|
break;
|
|
case EVENT_TYPE_UP:
|
|
_isSelected = false;
|
|
default:
|
|
break;
|
|
}
|
|
_pointerPosition = pt;
|
|
}
|
|
|
|
// Id of this shape
|
|
int CShape::GetObjectId() const
|
|
{
|
|
return _objectId;
|
|
}
|
|
|
|
// Shape label (applicable for controls)
|
|
void CShape::SetText(_In_z_ wchar_t const *text)
|
|
{
|
|
_text = text;
|
|
_textLen = 0;
|
|
while (_text[_textLen] != L'\0')
|
|
{
|
|
_textLen++;
|
|
}
|
|
}
|
|
|
|
// Shape polygonal representation
|
|
POLYGON *CShape::GetPolygon()
|
|
{
|
|
return &_polygonAbsolute;
|
|
}
|
|
|
|
// Shape type (e.g. regular shape or control shape)
|
|
OBJECT_TYPE CShape::GetObjectType() const
|
|
{
|
|
return _objectType;
|
|
}
|
|
|
|
// Update shape position to ensure that it stays within client screen
|
|
void CShape::_EnsureVisible()
|
|
{
|
|
// Initialize width height of object
|
|
float width = static_cast<float>(_polygonRelative.rcBound.right - _polygonRelative.rcBound.left) * _scale;
|
|
float height = static_cast<float>(_polygonRelative.rcBound.bottom - _polygonRelative.rcBound.top) * _scale;
|
|
|
|
float x = _xR + static_cast<float>(_polygonRelative.rcBound.left) * _scale;
|
|
x = max(0, min(x, static_cast<float>(_width) - width));
|
|
_xR = x - static_cast<float>(_polygonRelative.rcBound.left) * _scale;
|
|
|
|
float y = _yR + static_cast<float>(_polygonRelative.rcBound.top) * _scale;
|
|
y = max(0, min(y, static_cast<float>(_height) - height));
|
|
_yR = y - static_cast<float>(_polygonRelative.rcBound.top) * _scale;
|
|
}
|
|
|
|
// _Translate shape within client screen
|
|
void CShape::_Translate(_In_ float dX, _In_ float dY)
|
|
{
|
|
_xR += dX;
|
|
_yR += dY;
|
|
|
|
// Make sure object stays on screen
|
|
_EnsureVisible();
|
|
}
|
|
|
|
// Convert unscaled point to client coordinates
|
|
void CShape::_UnscaledToClient(_In_ POINT ptUnscaled, _Inout_ POINT *pClient)
|
|
{
|
|
pClient->x = static_cast<long>(_xR + ptUnscaled.x * _scale);
|
|
pClient->y = static_cast<long>(_yR + ptUnscaled.y * _scale);
|
|
}
|
|
|
|
// Convert unscaled point to client coordinates
|
|
D2D1_POINT_2F CShape::_UnscaledToClient(_In_ POINT unscaled)
|
|
{
|
|
return D2D1::Point2F(floor(_xR + unscaled.x * _scale), floor(_yR + unscaled.y * _scale));
|
|
}
|
|
|
|
// Convert unscaled rectangle to client coordinates
|
|
D2D1_RECT_F CShape::_UnscaledToClient(_In_ RECT unscaled)
|
|
{
|
|
return D2D1::RectF(
|
|
floor(_xR + unscaled.left * _scale),
|
|
floor(_yR + unscaled.top * _scale),
|
|
floor(_xR + unscaled.right * _scale),
|
|
floor(_yR + unscaled.bottom * _scale)
|
|
);
|
|
}
|