Click here to Skip to main content
15,891,951 members
Articles / Web Development / HTML

Mouse Gestures for Internet Explorer

Rate me:
Please Sign up or sign in to vote.
4.84/5 (99 votes)
21 Sep 200514 min read 1.3M   13.4K   235  
Adding mouse gesture recognition to Internet Explorer.
///////////////////////////////////////////////////////////////
//
// 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() );
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions