//////////////////////////////////////////////////////////////////////////
// Slider.cpp: Custom slider control.
// 
// 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 "MFPlayer.h"
#include <windowsx.h>

namespace SliderControl
{

    PCWSTR ClassName = L"SLIDER_CLASS";

    PCWSTR InstanceData = L"SLIDER_PROP";

    const LONG DEFAULT_MIN = 0;
    const LONG DEFAULT_MAX = 100;
    const LONG DEFAULT_THUMB = 0;


    struct Slider_Info
    {
        // Logical units
        LONG    posMin;     // minimum logical position
        LONG    posMax;     // maximum logical position
        LONG    posThumb;   // current logical position

        // Client area
        SIZE    pxThumbSize;    // real size of thumb bitmap (constant until bitmap changes
        RECT    rcThumb;        // client area of the thumb (changes with position)

        // state    
        BOOL    bThumbDown;     // User is dragging the thumb?

        // GDI objects
        HBRUSH  hBackground;
        HBITMAP hbmThumb;       // Thumb bitmap
    };


    LRESULT CALLBACK Slider_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    // Message handlers
    LRESULT OnCreate(HWND hwnd); 
    LRESULT OnNcDestroy(HWND hwnd, Slider_Info *pInfo); 
    LRESULT OnPaint(HWND hwnd, Slider_Info *pInfo); 
    LRESULT OnLButtonDown(HWND hwnd, LONG x, LONG y, Slider_Info *pInfo);
    LRESULT OnLButtonUp(HWND hwnd, LONG x, LONG y, Slider_Info *pInfo);
    LRESULT OnMouseMove(HWND hwnd, LONG x, LONG y, Slider_Info *pInfo);
    LRESULT OnReleaseSlider(HWND hwnd, Slider_Info *pInfo);

    // Private message handlers
    LRESULT OnSetThumbBitmap(HWND hwnd, WORD nID, Slider_Info *pInfo);
    LRESULT OnSetBackground(HWND hwnd, HBRUSH hBrush, Slider_Info *pInfo);
    LRESULT OnSetMinMax(HWND hwnd, LONG posMin, LONG posMax, Slider_Info *pInfo);   
    LRESULT OnSetPosition(HWND hwnd, LONG pos, Slider_Info *pInfo);
    LRESULT OnGetPosition(HWND hwnd, Slider_Info *pInfo);


    //--------------------------------------------------------------------------------------
    // SetInfo
    // Description: Store the control's instance information.
    //--------------------------------------------------------------------------------------

    inline BOOL SetInfo(HWND hwnd, Slider_Info *pInfo)
    {
        return SetProp(hwnd, InstanceData, pInfo);
    }

    //--------------------------------------------------------------------------------------
    // GetInfo
    // Description: Get the control's instance information.
    //--------------------------------------------------------------------------------------

    inline Slider_Info * GetInfo(HWND hwnd)
    {
        return (Slider_Info*)GetProp(hwnd, InstanceData);
    }

    //--------------------------------------------------------------------------------------
    // GetThumbRect
    // Description: Get the current position of the thumb rectangle.
    // 
    // The position is updated in the rcThumb member of pInfo.
    //--------------------------------------------------------------------------------------

    void GetThumbRect(HWND hwnd, Slider_Info *pInfo)
    {
        // 1. Convert logical position to pixels:
        //      logical width = max position - min position
        //      pixel width = client area - thumb width
        //      logical thumb position = current position - min position
        //      pixel thumb position = (logical thumb position / logical width) * pixel width

        RECT rc;
        GetClientRect(hwnd, &rc);

        LONG logWidth = pInfo->posMax - pInfo->posMin;
        LONG pixWidth = Width(rc) - pInfo->pxThumbSize.cx;
        LONG logPosition = pInfo->posThumb - pInfo->posMin;
        LONG left = MulDiv(logPosition, pixWidth, logWidth);    

        // 2. Center vertically
        LONG top = (Height(rc) - pInfo->pxThumbSize.cy) / 2;
    
        SetRect(
            &pInfo->rcThumb,
            left,
            top,
            left + pInfo->pxThumbSize.cx,
            top + pInfo->pxThumbSize.cy
            );
    }

    //--------------------------------------------------------------------------------------
    // PixelToLogical
    // Description: Convert a pixel position to a logical position.
    // 
    // Returns the logical position.
    //--------------------------------------------------------------------------------------

    LONG PixelToLogical(HWND hwnd, LONG x, Slider_Info *pInfo)
    {
        // % of pixel width * logical width, max = logical max

        // (x / pixel width) * logical width + logical min

        RECT rc;
        GetClientRect(hwnd, &rc);

        LONG pixWidth = Width(rc);
        LONG logWidth = pInfo->posMax - pInfo->posMin;
        LONG pos = MulDiv(x, logWidth, pixWidth) + pInfo->posMin;

        // clamp to slider min and max
        return max(pInfo->posMin, min(pos, pInfo->posMax));
    }

    //--------------------------------------------------------------------------------------
    // NotifyParent
    // Description: Send the parent window a WM_NOTIFY message with our current status.
    // 
    // hwnd: Control window.
    // code: WM_NOTIFY code. (One of the SLIDER_NOTIFY_xxx constants.)
    // pInfo: Instance data.
    //--------------------------------------------------------------------------------------
    void NotifyParent(HWND hwnd, UINT code, Slider_Info *pInfo)
    {
        HWND hParent = GetParent(hwnd);

        if (hParent)
        {   
            NMSLIDER_INFO nminfo;

            nminfo.hdr.hwndFrom = hwnd;
            nminfo.hdr.idFrom = (UINT_PTR)GetMenu(hwnd);  
            nminfo.hdr.code = code;
            nminfo.bSelected = pInfo->bThumbDown;
            nminfo.position = pInfo->posThumb;

            SendMessage(hParent, WM_NOTIFY, (WPARAM)nminfo.hdr.idFrom, (LPARAM)&nminfo);
        }
    }

    //--------------------------------------------------------------------------------------
    // Slider_WndProc
    // Description: Window proc for the control.
    //--------------------------------------------------------------------------------------

#include <strsafe.h>
    LRESULT CALLBACK Slider_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        Slider_Info * const pInfo = GetInfo(hwnd);

        switch (uMsg)
        {
        case WM_CREATE:
            return OnCreate(hwnd);

        case WM_PAINT:
            return OnPaint(hwnd, pInfo);

        case WM_NCDESTROY:
            return OnNcDestroy(hwnd, pInfo);

        case WM_LBUTTONDOWN:
            return OnLButtonDown(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), pInfo);

        case WM_LBUTTONUP:
            return OnLButtonUp(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), pInfo);

        case WM_MOUSEMOVE:
            return OnMouseMove(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), pInfo);

        case WM_ENABLE:
            if (wParam == FALSE) // Window is disabled. Stop tracking.
            {
                return OnReleaseSlider(hwnd, pInfo);
            }
            break;

        case WM_CAPTURECHANGED:
           // The window lost focus while the slider was tracking the mouse OR 
           // the slider released the mouse capture itself.
           return OnReleaseSlider(hwnd, pInfo);

            
        // Custom messages
        case WM_SLIDER_SET_THUMB_BITMAP:
            return OnSetThumbBitmap(hwnd, (WORD)wParam, pInfo);

        case WM_SLIDER_SET_BACKGROUND:
            return OnSetBackground(hwnd, (HBRUSH)wParam, pInfo);

        case WM_SLIDER_SET_MIN_MAX:
            return OnSetMinMax(hwnd, (LONG)wParam, (LONG)lParam, pInfo);

        case WM_SLIDER_SET_POSITION:
            return OnSetPosition(hwnd, (LONG)wParam, pInfo);

        case WM_SLIDER_GET_POSITION:
            return OnGetPosition(hwnd, pInfo);

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }

        return 0;
    };

    
    LRESULT OnCreate(HWND hwnd)
    {
        Slider_Info *pInfo = new (std::nothrow) Slider_Info;
        if (!pInfo)
        {
            return (LRESULT)-1;
        }

        ZeroMemory(pInfo, sizeof(Slider_Info));

        pInfo->posMin = DEFAULT_MIN;
        pInfo->posMax = DEFAULT_MAX;
        pInfo->posThumb = DEFAULT_THUMB;

        pInfo->bThumbDown = FALSE;

        pInfo->hBackground = CreateSolidBrush(RGB(0xFF, 0x80, 0x80));

        if (SetInfo(hwnd, pInfo))
        {
            return 0;
        }
        else
        {
            delete pInfo;
            return -1;
        }
    }

    LRESULT OnNcDestroy(HWND /*hwnd*/, Slider_Info *pInfo)
    {
        if (pInfo)
        {
            DeleteObject(pInfo->hBackground);
            DeleteObject(pInfo->hbmThumb);
            delete pInfo;
        }
        return 0;
    }


    LRESULT OnPaint(HWND hwnd, Slider_Info *pInfo)
    {
        PAINTSTRUCT ps;
        HDC hdc;

        hdc = BeginPaint(hwnd, &ps);

        // Draw the background
        if (pInfo->hBackground)
        {
            FillRect(hdc, &ps.rcPaint, pInfo->hBackground);
        }

        // Draw the thumb

        if (pInfo->hbmThumb)
        {
            HDC hdcCompat = CreateCompatibleDC(hdc); 
            SelectObject(hdcCompat, pInfo->hbmThumb); 

            (void)BitBlt(
                hdc, 
                pInfo->rcThumb.left, 
                pInfo->rcThumb.top, 
                pInfo->pxThumbSize.cx,
                pInfo->pxThumbSize.cy,
                hdcCompat,
                0, 0,
                SRCCOPY
                );

            DeleteDC(hdcCompat);
        }

        EndPaint(hwnd, &ps);
        return 0;
    }


    void SetSliderPosition(HWND hwnd, LONG pos, Slider_Info *pInfo)
    {
        // Invalidate the old thumb rect
        InvalidateRect(hwnd, &pInfo->rcThumb, FALSE);

        pInfo->posThumb = pos;

        GetThumbRect(hwnd, pInfo);

        // Invalidate the new thumb rect
        InvalidateRect(hwnd, &pInfo->rcThumb, FALSE);
    }



    LRESULT OnLButtonDown(HWND hwnd, LONG x, LONG /*y*/, Slider_Info *pInfo)
    {
        // Move the slider to the mouse position.
        SetSliderPosition(hwnd, PixelToLogical(hwnd, x, pInfo), pInfo);

        // Set the thumb-down flag.
        pInfo->bThumbDown = TRUE;

        // Start capturing mouse moves so we can update the slider position.
        SetCapture(hwnd);

        // Notify the owner window that the control was selected.
        NotifyParent(hwnd, SLIDER_NOTIFY_SELECT, pInfo);

        return 0;
    }

    LRESULT OnLButtonUp(HWND hwnd, LONG /*x*/, LONG /*y*/, Slider_Info *pInfo)
    {
        return OnReleaseSlider(hwnd, pInfo);
    }

    LRESULT OnMouseMove(HWND hwnd, LONG x, LONG /*y*/, Slider_Info *pInfo)
    {
        // If the control is selected, update the slider position
        // and notify the owner window.
        if (pInfo->bThumbDown)
        {
            SetSliderPosition(hwnd, PixelToLogical(hwnd, x, pInfo), pInfo);

            NotifyParent(hwnd, SLIDER_NOTIFY_DRAG, pInfo);

        }
        return 0;
    }

    // OnReleaseSlider: Stop tracking the slider movement.
    LRESULT OnReleaseSlider(HWND hwnd, Slider_Info *pInfo)
    {
        if (pInfo->bThumbDown)
        {
            // Reset the thumb-down flag.
            pInfo->bThumbDown = FALSE;

            InvalidateRect(hwnd, &pInfo->rcThumb, FALSE);

            // Stop capturing mouse moves.
            ReleaseCapture();

            // Notify the owner window that the control was deselected.
            NotifyParent(hwnd, SLIDER_NOTIFY_RELEASE, pInfo);
        }
        return 0;
    }

    //--------------------------------------------------------------------------------------
    // OnSetThumbBitmap
    // Description: Sets the bitmap image for the slider thumb.
    // 
    // Handler for WM_SLIDER_SET_THUMB_BITMAP message.
    //--------------------------------------------------------------------------------------

    LRESULT OnSetThumbBitmap(HWND hwnd, WORD nID, Slider_Info *pInfo)
    {
        HBITMAP hbm = LoadBitmap(GetInstance(), MAKEINTRESOURCE(nID));

        if (hbm == NULL)
        {
            return FALSE;
        }

        BITMAP bm;
        GetObject(hbm, sizeof(BITMAP), &bm);

        pInfo->pxThumbSize.cx = bm.bmWidth;
        pInfo->pxThumbSize.cy = bm.bmHeight;

        if (pInfo->hbmThumb)
        {
            DeleteObject(pInfo->hbmThumb);
        }

        pInfo->hbmThumb = hbm;

        GetThumbRect(hwnd, pInfo);

        return TRUE;
    }

    //--------------------------------------------------------------------------------------
    // OnSetBackground
    // Description: Sets the background brush.
    // 
    // Handler for WM_SLIDER_SET_BACKGROUND message.
    //--------------------------------------------------------------------------------------

    LRESULT OnSetBackground(HWND /*hwnd*/, HBRUSH hBrush, Slider_Info *pInfo)
    {
        if (pInfo->hBackground)
        {
            DeleteObject(pInfo->hBackground);
        }

        pInfo->hBackground = hBrush;
    
        return TRUE;
    }


    //--------------------------------------------------------------------------------------
    // OnSetMinMax
    // Description: Sets the slider range.
    // 
    // Handler for WM_SLIDER_SET_MIN_MAX message.
    //--------------------------------------------------------------------------------------
    LRESULT OnSetMinMax(HWND hwnd, LONG posMin, LONG posMax, Slider_Info *pInfo)
    {
        pInfo->posMin = posMin;
        pInfo->posMax = posMax;

        if (pInfo->posThumb < posMin)
        {
            SetSliderPosition(hwnd, posMin, pInfo);
        }
        else if (pInfo->posThumb > posMax)
        {
            SetSliderPosition(hwnd, posMax, pInfo);
        }
        return TRUE;
    }

    //--------------------------------------------------------------------------------------
    // OnSetPosition
    // Description: Sets the slider range.
    // 
    // Handler for WM_SLIDER_SET_POSITION message.
    //--------------------------------------------------------------------------------------

    LRESULT OnSetPosition(HWND hwnd, LONG pos, Slider_Info *pInfo)
    {
        if (pos < pInfo->posMin || pos > pInfo->posMax)
        {
            return FALSE;
        }
        else
        {
            SetSliderPosition(hwnd, pos, pInfo);
            return TRUE;
        }
    }

    //--------------------------------------------------------------------------------------
    // OnSetPosition
    // Description: Sets the slider range.
    // 
    // Handler for WM_SLIDER_GET_POSITION message.
    //--------------------------------------------------------------------------------------

    LRESULT OnGetPosition(HWND /*hwnd*/, Slider_Info *pInfo)
    {
        return pInfo->posThumb;
    }

}  // namespace SliderControl




//--------------------------------------------------------------------------------------
// Slider_Init
// Description: Initializes the slider window class.
// 
// Call this function before using the slider control.
//--------------------------------------------------------------------------------------

HRESULT Slider_Init()
{
    WNDCLASSEX wce;
    ZeroMemory(&wce, sizeof(wce));

    wce.cbSize = sizeof(WNDCLASSEX);
    wce.lpfnWndProc = SliderControl::Slider_WndProc;
    wce.hInstance = GetInstance();
    wce.lpszClassName = SliderControl::ClassName;
    wce.cbWndExtra = sizeof(SliderControl::Slider_Info);        // Reserve space for slider instance data

    ATOM a = RegisterClassEx(&wce);

    if (a == 0)
    {
        return __HRESULT_FROM_WIN32(GetLastError());
    }
    else
    {
        return S_OK;
    }
}




