///////////////////////////////////////////////////////////////
//
// TrailWindow.cpp
//
// Created: 28/06/2005
// Copyright (c) 2005 Ralph Hare (ralph.hare@ysgyfarnog.co.uk)
// All rights reserved.
//
// The code and information is provided "as-is" without
// warranty of any kind, either expressed or implied.
//
///////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include "TrailWindow.h"
#include "GDIUtils.h"
#include <math.h>
namespace
{
const int GUTTER = -1;
const COLORREF BACK_COLOUR = RGB( 255, 0, 255 ); // Magenta
const double PI = 3.1415926535897932384626433832795;
const UINT HIDE_TIMER = 0x05091946;
inline POINT TransformPoint( const POINT &pt, const SIZE &offset )
{
return WTL::CPoint( pt.x + offset.cx, pt.y + offset.cy );
}
inline double Distance( const POINT &pt1, const POINT &pt2 )
{
return sqrt( pow( pt1.x - pt2.x, 2 ) + pow( pt1.y - pt2.y, 2 ) );
}
inline double DegToRad( double deg )
{
return deg * ( ( 2 * PI ) / 360.0 );
}
inline double RadToDeg( double rad )
{
return rad * ( 360.0 / ( 2 * PI ) );
}
class EndMarker
{
public:
EndMarker( int penWidth_, WTL::CPoint ptOrigin, double angle )
{
//
// the min pen width is 2 pixels
//
int penWidth = std::max( penWidth_, 2 );
static const double scale = 2.5;
static const double cos30 = scale * cos( PI / 6 );
static const double sin30 = scale * sin( PI / 6 );
//
// adjust the origin, so the marker is central
//
int nx = static_cast< int >( penWidth * 1.5 * cos( angle ) );
int ny = static_cast< int >( penWidth * 1.5 * sin( angle ) );
ptOrigin.Offset( nx, ny );
//
// calculate the magnitude of each vertex
//
double dx = -penWidth * cos30;
double dy = +penWidth * sin30;
m_pts[ 0 ] = ptOrigin;
m_pts[ 1 ] = ptOrigin;
m_pts[ 2 ] = ptOrigin;
//
// rotate the vertices
//
const double cosA = cos( angle );
const double sinA = sin( angle );
m_pts[ 1 ].Offset(
static_cast< int >( ( dx * cosA ) - ( dy * sinA ) ),
static_cast< int >( ( dx * sinA ) + ( dy * cosA ) )
);
m_pts[ 2 ].Offset(
static_cast< int >( ( dx * cosA ) - ( -dy * sinA ) ),
static_cast< int >( ( dx * sinA ) + ( -dy * cosA ) )
);
}
void Draw( HDC hDC )
{
::Polygon( hDC, m_pts, 3 );
}
public:
WTL::CPoint m_pts[ 3 ];
};
inline double GetAngle( const std::vector< POINT > &path )
{
if( path.size() < 2 )
{
return 0.0;
}
const POINT &pt1 = *( path.end() - 1 ); // == path.back();
const POINT &pt2 = *( path.end() - 2 ); // == path.back() - 1;
double dx = pt1.x - pt2.x;
double dy = pt1.y - pt2.y;
if( dx == 0.0 )
{
return dy > 0 ? ( PI / 2 ) : ( -PI / 2 );
}
else if( dy == 0.0 )
{
return dx > 0 ? 0 : PI;
}
double ang = atan( dy / dx );
if( dx < 0 )
{
return ang + ( dy < 0 ? PI : -PI );
}
return ang;
}
HBRUSH GetBackBrush()
{
static WTL::CBrush backBrush( ::CreateSolidBrush( BACK_COLOUR ) );
return backBrush;
}
HPEN CreatePenEx( int style, int width, COLORREF clr )
{
LOGBRUSH lb = { BS_SOLID, clr, 0 };
style |= PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_ROUND;
return ::ExtCreatePen( style, width, &lb, 0, NULL );
}
}
TrailWindow::TrailWindow( COLORREF clr, long thickness, BYTE transparency, long fadeSpeed ) :
CWindowImpl< TrailWindow >(),
m_offset( 0, 0 ),
m_lastPoint( INT_MIN, INT_MIN ),
m_path(),
m_memDC(),
m_bitmap(),
m_pen( CreatePenEx( 0, thickness, clr ) ),
m_penEnd( CreatePenEx( 0, 0, clr ) ),
m_brush( ::CreateSolidBrush( clr ) ),
m_rcWnd( 0, 0, 0, 0 ),
m_rcExtents( 0, 0, 0, 0 ),
m_penWidth( 10 ),
m_transparency( transparency ),
m_fadeIncrement( std::max( fadeSpeed, 1L ) ),
m_sensitivity( 5.0 )
{
::GetWindowRect( ::GetDesktopWindow(), &m_rcWnd );
m_rcWnd.InflateRect( GUTTER, GUTTER );
if( CWindowImpl< TrailWindow >::Create(
NULL,
m_rcWnd,
NULL,
WS_POPUP,
WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_LAYERED
) == NULL )
{
ATLASSERT( FALSE );
}
SetTransparency( m_transparency );
ShowWindow( SW_SHOWNA );
UpdateBitmap();
}
TrailWindow::~TrailWindow()
{
if( ::IsWindow( m_hWnd ) )
{
DestroyWindow();
}
}
CWndClassInfo & TrailWindow::GetWndClassInfo()
{
//
// ATL Internals pp 419
//
static CWndClassInfo wc =
{
{
sizeof( WNDCLASSEX ),
0,
StartWindowProc,
0,
0,
NULL,
NULL,
NULL,
GetBackBrush(),
NULL,
_T( "TrailWindow" ),
NULL
},
NULL,
NULL,
IDC_ARROW,
TRUE,
0,
_T( "TrailWindow" )
};
return wc;
}
void TrailWindow::SetTransparency( BYTE transparency )
{
ATLASSERT( m_hWnd && ::IsWindow( m_hWnd ) );
GDIUtils::SetLayeredWindowAttribute(
m_hWnd,
BACK_COLOUR,
transparency,
LWA_ALPHA | LWA_COLORKEY
);
}
void TrailWindow::DrawTrail( HDC hDC )
{
if( m_path.empty() == false )
{
//
// get the bounding rect for the trail - allow m_penWidth pixels
// either side, but don't exceed the limits of the trail window
//
long border = std::max( m_penWidth * 2, 1L );
WTL::CRect rcExtents = m_rcExtents;
rcExtents.left = std::max( m_rcExtents.left - border, m_rcWnd.left );
rcExtents.right = std::min( m_rcExtents.right + border, m_rcWnd.right );
rcExtents.top = std::max( m_rcExtents.top - border, m_rcWnd.top );
rcExtents.bottom = std::min( m_rcExtents.bottom + ( border * 2 ), m_rcWnd.bottom );
//
// prepare to draw the trail
//
HBITMAP bmOld = m_memDC.SelectBitmap( m_bitmap );
m_memDC.FillRect( &rcExtents, GetBackBrush() );
HPEN penOld = m_memDC.SelectPen( m_pen );
HBRUSH brOld = m_memDC.SelectBrush( m_brush );
//
// first the dot at the beginning
//
long radius = std::max( m_penWidth / 2, 2L );
RECT rcBegin;
rcBegin.left = m_path.front().x - radius;
rcBegin.right = m_path.front().x + radius;
rcBegin.top = m_path.front().y - radius;
rcBegin.bottom = m_path.front().y + radius;
m_memDC.Ellipse( &rcBegin );
//
// draw the trail
//
m_memDC.Polyline( &m_path[ 0 ], static_cast< int >( m_path.size() ) );
//
// only draw the end marker if the mouse has been moved
// sufficiently
//
if( ( m_path.size() > 5 ) ||
( Distance( m_path.front(), m_path.back() ) > m_penWidth ) )
{
EndMarker endMarker( m_penWidth, m_path.back(), GetAngle( m_path ) );
m_memDC.SelectPen( m_penEnd );
endMarker.Draw( m_memDC );
}
//
// blit in the memory dc
//
::BitBlt(
hDC,
rcExtents.left,
rcExtents.top,
rcExtents.Width(),
rcExtents.Height(),
m_memDC,
rcExtents.left,
rcExtents.top,
SRCCOPY
);
m_memDC.SelectBrush( brOld );
m_memDC.SelectPen( penOld );
m_memDC.SelectBitmap( bmOld );
}
}
void TrailWindow::Begin( HWND hWnd )
{
//
// store the top-left corner of the window we're drawing over
// (the path we receive is relative to this window, and we'll
// need to transform the coordinates before drawing the trail)
//
WTL::CRect rc;
::GetWindowRect( hWnd, &rc );
m_offset = *( reinterpret_cast< SIZE * >( &rc ) );
//
// reset the last point to a point well off the screen, ensuring
// we definitely draw the trail on the first Update
//
m_lastPoint = WTL::CPoint( INT_MIN, INT_MIN );
//
// show the window, but don't activate it. this means the apparent
// focus remains with the window we're drawing the trail over
//
SetWindowPos(
HWND_TOPMOST,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS
);
}
void TrailWindow::End()
{
//
// fade the window
//
if( m_fadeIncrement != 255 )
{
for( int idx = m_transparency; idx > 0; idx -= m_fadeIncrement )
{
SetTransparency( static_cast< BYTE >( idx ) );
}
}
//
// clear the path, and invalidate the window. this forces a
// redraw before we hide the window (but see the message pump
// below), so it comes up clear next time round
//
m_path.clear();
Invalidate( TRUE );
//
// Pump the queue to ensure that the window processes
// the invalidate message before we hide
//
MSG msg = { 0 };
while( ::PeekMessage( &msg, m_hWnd, 0, 0, PM_REMOVE ) )
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
//
// reset the transparency
//
if( m_fadeIncrement != 255 )
{
SetTransparency( m_transparency );
}
}
void TrailWindow::Update( const Path &path )
{
//
// Always add the first point
//
if( m_path.empty() )
{
m_path.push_back( TransformPoint( path.front(), m_offset ) );
m_lastPoint = path.front();
m_rcExtents.left = m_path.back().x;
m_rcExtents.right = m_path.back().x;
m_rcExtents.top = m_path.back().y;
m_rcExtents.bottom = m_path.back().y;
}
else
{
//
// if we haven't moved more than m_sensitivity pixels
// since last time, then there's nothing to draw
//
if( Distance( m_lastPoint, path.back() ) < m_sensitivity )
{
return;
}
//
// add the new point, and update our bounding rect
//
m_path.push_back( TransformPoint( path.back(), m_offset ) );
m_lastPoint = path.back();
if( m_path.back().x > m_rcExtents.right )
{
m_rcExtents.right = m_path.back().x;
}
else if( m_path.back().x < m_rcExtents.left )
{
m_rcExtents.left = m_path.back().x;
}
if( m_path.back().y < m_rcExtents.top )
{
m_rcExtents.top = m_path.back().y;
}
else if( m_path.back().y > m_rcExtents.bottom )
{
m_rcExtents.bottom = m_path.back().y;
}
}
//
// draw the trail
//
WTL::CClientDC dc( m_hWnd );
DrawTrail( dc );
}
void TrailWindow::UpdateSettings( COLORREF clr, long thickness, BYTE transparency, long fadeSpeed )
{
m_pen.DeleteObject();
m_pen.Attach( CreatePenEx( 0, thickness, clr ) );
m_penWidth = thickness;
m_penEnd.DeleteObject();
m_penEnd.Attach( CreatePenEx( 0, 0, clr ) );
m_brush.DeleteObject();
m_brush.CreateSolidBrush( clr );
m_transparency = transparency;
SetTransparency( m_transparency );
m_fadeIncrement = std::max( fadeSpeed, 1L );
}
void TrailWindow::Show()
{
CriticalSection::Lock lock( m_csShow );
SetWindowPos( HWND_TOPMOST, &m_rcWnd, SWP_NOACTIVATE | SWP_NOCOPYBITS );
KillTimer( HIDE_TIMER );
}
void TrailWindow::Hide()
{
CriticalSection::Lock lock( m_csShow );
SetTimer( HIDE_TIMER, 500, NULL );
}
LRESULT TrailWindow::OnTimer( UINT, WPARAM, LPARAM, BOOL & )
{
CriticalSection::Lock lock( m_csShow );
KillTimer( HIDE_TIMER );
SetWindowPos(
HWND_BOTTOM,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS
);
return 0;
}
LRESULT TrailWindow::OnDisplayChange( UINT, WPARAM wParam, LPARAM, BOOL & )
{
UpdateBitmap();
return 0;
}
void TrailWindow::UpdateBitmap()
{
if( m_memDC.IsNull() == false )
{
m_memDC.DeleteDC();
}
if( m_bitmap.IsNull() == false )
{
m_bitmap.DeleteObject();
}
WTL::CClientDC dc( m_hWnd );
m_memDC.CreateCompatibleDC( dc );
::GetWindowRect( ::GetDesktopWindow(), &m_rcWnd );
m_rcWnd.InflateRect( GUTTER, GUTTER );
SetWindowPos( HWND_TOPMOST, &m_rcWnd, SWP_NOACTIVATE );
m_bitmap.CreateCompatibleBitmap( dc, m_rcWnd.Width(), m_rcWnd.Height() );
}