// File: GamePad.cpp

#include "pch.h"

#include "GamePad.h"
#include "PlatformHelpers.h"

using namespace DirectX;
using Microsoft::WRL::ComPtr;

namespace
{
    const float c_XboxOneThumbDeadZone = .24f;  // Recommended Xbox One controller deadzone

    float ApplyLinearDeadZone(float value, float maxValue, float deadZoneSize)
    {
        if (value < -deadZoneSize)
        {
            // Increase negative values to remove the deadzone discontinuity.
            value += deadZoneSize;
        }
        else if (value > deadZoneSize)
        {
            // Decrease positive values to remove the deadzone discontinuity.
            value -= deadZoneSize;
        }
        else
        {
            // Values inside the deadzone come out zero.
            return 0;
        }

        float scaledValue = value / (maxValue - deadZoneSize);
        return std::max(-1.f, std::min(scaledValue, 1.f));
    }

    void ApplyStickDeadZone(float x, float y, GamePad::DeadZone deadZoneMode, float maxValue, float deadZoneSize,
                            _Out_ float& resultX, _Out_ float& resultY)
    {
        switch (deadZoneMode)
        {
            case GamePad::DEAD_ZONE_INDEPENDENT_AXES:
                resultX = ApplyLinearDeadZone(x, maxValue, deadZoneSize);
                resultY = ApplyLinearDeadZone(y, maxValue, deadZoneSize);
                break;

            case GamePad::DEAD_ZONE_CIRCULAR:
            {
                float dist = sqrtf(x*x + y * y);
                float wanted = ApplyLinearDeadZone(dist, maxValue, deadZoneSize);

                float scale = (wanted > 0.f) ? (wanted / dist) : 0.f;

                resultX = std::max(-1.f, std::min(x * scale, 1.f));
                resultY = std::max(-1.f, std::min(y * scale, 1.f));
            }
            break;

            default: // GamePad::DEAD_ZONE_NONE
                resultX = ApplyLinearDeadZone(x, maxValue, 0);
                resultY = ApplyLinearDeadZone(y, maxValue, 0);
                break;
        }
    }
}

#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10)

#pragma warning(push)
#pragma warning(disable : 4471)
#include <Windows.Gaming.Input.h>
#pragma warning(pop)

class GamePad::Impl
{
public:
    Impl(GamePad* owner) :
        mOwner(owner),
        mCtrlChanged(INVALID_HANDLE_VALUE),
        mUserChanged(INVALID_HANDLE_VALUE),
        mMostRecentGamepad(0),
        mStatics{},
        mGamePad{},
        mUserChangeToken{},
        mAddedToken{},
        mRemovedToken{},
        mChanged{}
    {
        using namespace Microsoft::WRL;
        using namespace Microsoft::WRL::Wrappers;
        using namespace ABI::Windows::Foundation;

        if (s_gamePad)
        {
            throw std::exception("GamePad is a singleton");
        }

        s_gamePad = this;

        mChanged.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
        if (!mChanged)
        {
            throw std::exception("CreateEventEx");
        }

        ThrowIfFailed(GetActivationFactory(HStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad).Get(), mStatics.GetAddressOf()));

        typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad AddedHandler;
        ThrowIfFailed(mStatics->add_GamepadAdded(Callback<AddedHandler>(GamepadAdded).Get(), &mAddedToken));

        typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad RemovedHandler;
        ThrowIfFailed(mStatics->add_GamepadRemoved(Callback<RemovedHandler>(GamepadRemoved).Get(), &mRemovedToken));

        ScanGamePads();
    }

    ~Impl()
    {
        using namespace ABI::Windows::Gaming::Input;

        for (size_t j = 0; j < MAX_PLAYER_COUNT; ++j)
        {
            if (mGamePad[j])
            {
                ComPtr<IGameController> ctrl;
                HRESULT hr = mGamePad[j].As(&ctrl);
                if (SUCCEEDED(hr) && ctrl)
                {
                    (void)ctrl->remove_UserChanged(mUserChangeToken[j]);
                    mUserChangeToken[j].value = 0;
                }

                mGamePad[j].Reset();
            }
        }

        if (mStatics)
        {
            (void)mStatics->remove_GamepadAdded(mAddedToken);
            mAddedToken.value = 0;

            (void)mStatics->remove_GamepadRemoved(mRemovedToken);
            mRemovedToken.value = 0;

            mStatics.Reset();
        }

        s_gamePad = nullptr;
    }

    void GetState(int player, _Out_ State& state, DeadZone deadZoneMode)
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::Gaming::Input;

        if (WaitForSingleObjectEx(mChanged.get(), 0, FALSE) == WAIT_OBJECT_0)
        {
            ScanGamePads();
        }

        if (player == -1)
            player = mMostRecentGamepad;

        if ((player >= 0) && (player < MAX_PLAYER_COUNT))
        {
            if (mGamePad[player])
            {
                GamepadReading reading;
                HRESULT hr = mGamePad[player]->GetCurrentReading(&reading);
                if (SUCCEEDED(hr))
                {
                    state.connected = true;
                    state.packet = reading.Timestamp;

                    state.buttons.a = (reading.Buttons & GamepadButtons::GamepadButtons_A) != 0;
                    state.buttons.b = (reading.Buttons & GamepadButtons::GamepadButtons_B) != 0;
                    state.buttons.x = (reading.Buttons & GamepadButtons::GamepadButtons_X) != 0;
                    state.buttons.y = (reading.Buttons & GamepadButtons::GamepadButtons_Y) != 0;

                    state.buttons.leftStick = (reading.Buttons & GamepadButtons::GamepadButtons_LeftThumbstick) != 0;
                    state.buttons.rightStick = (reading.Buttons & GamepadButtons::GamepadButtons_RightThumbstick) != 0;

                    state.buttons.leftShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_LeftShoulder) != 0;
                    state.buttons.rightShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_RightShoulder) != 0;

                    state.buttons.back = (reading.Buttons & GamepadButtons::GamepadButtons_View) != 0;
                    state.buttons.start = (reading.Buttons & GamepadButtons::GamepadButtons_Menu) != 0;

                    state.dpad.up = (reading.Buttons & GamepadButtons::GamepadButtons_DPadUp) != 0;
                    state.dpad.down = (reading.Buttons & GamepadButtons::GamepadButtons_DPadDown) != 0;
                    state.dpad.right = (reading.Buttons & GamepadButtons::GamepadButtons_DPadRight) != 0;
                    state.dpad.left = (reading.Buttons & GamepadButtons::GamepadButtons_DPadLeft) != 0;

                    ApplyStickDeadZone(static_cast<float>(reading.LeftThumbstickX), static_cast<float>(reading.LeftThumbstickY),
                                       deadZoneMode, 1.f, c_XboxOneThumbDeadZone,
                                       state.thumbSticks.leftX, state.thumbSticks.leftY);

                    ApplyStickDeadZone(static_cast<float>(reading.RightThumbstickX), static_cast<float>(reading.RightThumbstickY),
                                       deadZoneMode, 1.f, c_XboxOneThumbDeadZone,
                                       state.thumbSticks.rightX, state.thumbSticks.rightY);

                    state.triggers.left = static_cast<float>(reading.LeftTrigger);
                    state.triggers.right = static_cast<float>(reading.RightTrigger);

                    return;
                }
            }
        }

        memset(&state, 0, sizeof(State));
    }

    void GetCapabilities(int player, Capabilities& caps)
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::System;
        using namespace ABI::Windows::Gaming::Input;

        if (WaitForSingleObjectEx(mChanged.get(), 0, FALSE) == WAIT_OBJECT_0)
        {
            ScanGamePads();
        }

        if (player == -1)
            player = mMostRecentGamepad;

        if ((player >= 0) && (player < MAX_PLAYER_COUNT))
        {
            if (mGamePad[player])
            {
                caps.connected = true;
                caps.gamepadType = Capabilities::GAMEPAD;
                caps.id.clear();

                ComPtr<IGameController> ctrl;
                HRESULT hr = mGamePad[player].As(&ctrl);
                if (SUCCEEDED(hr) && ctrl)
                {
                    ComPtr<IUser> user;
                    hr = ctrl->get_User(user.GetAddressOf());
                    if (SUCCEEDED(hr) && user != nullptr)
                    {
                        Wrappers::HString str;
                        hr = user->get_NonRoamableId(str.GetAddressOf());
                        if (SUCCEEDED(hr))
                        {
                            caps.id = str.GetRawBuffer(nullptr);
                        }
                    }
                }
                return;
            }
        }

        caps.id.clear();
        caps = {};
    }

    bool SetVibration(int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger)
    {
        using namespace ABI::Windows::Gaming::Input;

        if (player == -1)
            player = mMostRecentGamepad;

        if ((player >= 0) && (player < MAX_PLAYER_COUNT))
        {
            if (mGamePad[player])
            {
                GamepadVibration vib;
                vib.LeftMotor = leftMotor;
                vib.RightMotor = rightMotor;
                vib.LeftTrigger = leftTrigger;
                vib.RightTrigger = rightTrigger;
                HRESULT hr = mGamePad[player]->put_Vibration(vib);

                if (SUCCEEDED(hr))
                    return true;
            }
        }

        return false;
    }

    void Suspend()
    {
        for (size_t j = 0; j < MAX_PLAYER_COUNT; ++j)
        {
            mGamePad[j].Reset();
        }
    }

    void Resume()
    {
        // Make sure we rescan gamepads
        SetEvent(mChanged.get());
    }

    GamePad*    mOwner;

    static GamePad::Impl* s_gamePad;

    HANDLE mCtrlChanged;
    HANDLE mUserChanged;

private:
    int mMostRecentGamepad;

    void ScanGamePads()
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::Foundation::Collections;
        using namespace ABI::Windows::Gaming::Input;

        ComPtr<IVectorView<Gamepad*>> pads;
        ThrowIfFailed(mStatics->get_Gamepads(pads.GetAddressOf()));

        unsigned int count = 0;
        ThrowIfFailed(pads->get_Size(&count));

        // Check for removed gamepads
        for (size_t j = 0; j < MAX_PLAYER_COUNT; ++j)
        {
            if (mGamePad[j])
            {
                unsigned int k = 0;
                for (; k < count; ++k)
                {
                    ComPtr<IGamepad> pad;
                    HRESULT hr = pads->GetAt(k, pad.GetAddressOf());
                    if (SUCCEEDED(hr) && (pad == mGamePad[j]))
                    {
                        break;
                    }
                }

                if (k >= count)
                {
                    ComPtr<IGameController> ctrl;
                    HRESULT hr = mGamePad[j].As(&ctrl);
                    if (SUCCEEDED(hr) && ctrl)
                    {
                        (void)ctrl->remove_UserChanged(mUserChangeToken[j]);
                        mUserChangeToken[j].value = 0;
                    }

                    mGamePad[j].Reset();
                }
            }
        }

        // Check for added gamepads
        for (unsigned int j = 0; j < count; ++j)
        {
            ComPtr<IGamepad> pad;
            HRESULT hr = pads->GetAt(j, pad.GetAddressOf());
            if (SUCCEEDED(hr))
            {
                size_t empty = MAX_PLAYER_COUNT;
                size_t k = 0;
                for (; k < MAX_PLAYER_COUNT; ++k)
                {
                    if (mGamePad[k] == pad)
                    {
                        if (j == (count - 1))
                            mMostRecentGamepad = static_cast<int>(k);
                        break;
                    }
                    else if (!mGamePad[k])
                    {
                        if (empty >= MAX_PLAYER_COUNT)
                            empty = k;
                    }
                }

                if (k >= MAX_PLAYER_COUNT)
                {
                    // Silently ignore "extra" gamepads as there's no hard limit
                    if (empty < MAX_PLAYER_COUNT)
                    {
                        mGamePad[empty] = pad;
                        if (j == (count - 1))
                            mMostRecentGamepad = static_cast<int>(empty);

                        ComPtr<IGameController> ctrl;
                        hr = pad.As(&ctrl);
                        if (SUCCEEDED(hr) && ctrl)
                        {
                            typedef __FITypedEventHandler_2_Windows__CGaming__CInput__CIGameController_Windows__CSystem__CUserChangedEventArgs UserHandler;
                            ThrowIfFailed(ctrl->add_UserChanged(Callback<UserHandler>(UserChanged).Get(), &mUserChangeToken[empty]));
                        }
                    }
                }
            }
        }
    }

    ComPtr<ABI::Windows::Gaming::Input::IGamepadStatics> mStatics;
    ComPtr<ABI::Windows::Gaming::Input::IGamepad> mGamePad[MAX_PLAYER_COUNT];
    EventRegistrationToken mUserChangeToken[MAX_PLAYER_COUNT];

    EventRegistrationToken mAddedToken;
    EventRegistrationToken mRemovedToken;

    ScopedHandle mChanged;

    static HRESULT GamepadAdded(IInspectable *, ABI::Windows::Gaming::Input::IGamepad*)
    {
        if (s_gamePad)
        {
            SetEvent(s_gamePad->mChanged.get());

            if (s_gamePad->mCtrlChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent(s_gamePad->mCtrlChanged);
            }
        }
        return S_OK;
    }

    static HRESULT GamepadRemoved(IInspectable *, ABI::Windows::Gaming::Input::IGamepad*)
    {
        if (s_gamePad)
        {
            SetEvent(s_gamePad->mChanged.get());

            if (s_gamePad->mCtrlChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent(s_gamePad->mCtrlChanged);
            }
        }
        return S_OK;
    }

    static HRESULT UserChanged(ABI::Windows::Gaming::Input::IGameController*, ABI::Windows::System::IUserChangedEventArgs*)
    {
        if (s_gamePad)
        {
            if (s_gamePad->mUserChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent(s_gamePad->mUserChanged);
            }
        }
        return S_OK;
    }
};

GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;

#elif defined(_XBOX_ONE)

#include <Windows.Xbox.Input.h>

#include <Windows.Foundation.Collections.h>

class GamePad::Impl
{
public:
    class GamepadAddedListener : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
        ABI::Windows::Foundation::IEventHandler<ABI::Windows::Xbox::Input::GamepadAddedEventArgs *>,
        Microsoft::WRL::FtmBase>
    {
    public:
        GamepadAddedListener(HANDLE event) : mEvent(event) {}

        STDMETHOD(Invoke)(_In_ IInspectable *, _In_ ABI::Windows::Xbox::Input::IGamepadAddedEventArgs *) override
        {
            SetEvent(mEvent);

            auto pad = GamePad::Impl::s_gamePad;

            if (pad && pad->mCtrlChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent(pad->mCtrlChanged);
            }
            return S_OK;
        }

    private:
        HANDLE mEvent;
    };

    class GamepadRemovedListener : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
        ABI::Windows::Foundation::IEventHandler<ABI::Windows::Xbox::Input::GamepadRemovedEventArgs *>,
        Microsoft::WRL::FtmBase>
    {
    public:
        GamepadRemovedListener(HANDLE event) : mEvent(event) {}

        STDMETHOD(Invoke)(_In_ IInspectable *, _In_ ABI::Windows::Xbox::Input::IGamepadRemovedEventArgs *) override
        {
            SetEvent(mEvent);

            auto pad = GamePad::Impl::s_gamePad;

            if (pad && pad->mCtrlChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent(pad->mCtrlChanged);
            }
            return S_OK;
        }

    private:
        HANDLE mEvent;
    };

    class UserPairingListener : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
        ABI::Windows::Foundation::IEventHandler<ABI::Windows::Xbox::Input::ControllerPairingChangedEventArgs *>,
        Microsoft::WRL::FtmBase>
    {
    public:
        UserPairingListener() noexcept {}

        STDMETHOD(Invoke)(_In_ IInspectable *, _In_ ABI::Windows::Xbox::Input::IControllerPairingChangedEventArgs *) override
        {
            auto pad = GamePad::Impl::s_gamePad;

            if (pad && pad->mUserChanged != INVALID_HANDLE_VALUE)
            {
                SetEvent(pad->mUserChanged);
            }
            return S_OK;
        }
    };

    Impl(GamePad *owner) :
        mOwner(owner),
        mCtrlChanged(INVALID_HANDLE_VALUE),
        mUserChanged(INVALID_HANDLE_VALUE),
        mMostRecentGamepad(0),
        mStatics{},
        mStaticsCtrl{},
        mGamePad{},
        mAddedToken{},
        mRemovedToken{},
        mUserParingToken{},
        mChanged{}
    {
        using namespace Microsoft::WRL;
        using namespace Microsoft::WRL::Wrappers;
        using namespace ABI::Windows::Foundation;

        if (s_gamePad)
        {
            throw std::exception("GamePad is a singleton");
        }

        s_gamePad = this;

        mChanged.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
        if (!mChanged)
        {
            throw std::exception("CreateEventEx");
        }

        ThrowIfFailed(GetActivationFactory(HStringReference(RuntimeClass_Windows_Xbox_Input_Gamepad).Get(), mStatics.GetAddressOf()));

        ThrowIfFailed(GetActivationFactory(HStringReference(RuntimeClass_Windows_Xbox_Input_Controller).Get(), mStaticsCtrl.GetAddressOf()));

        ThrowIfFailed(mStatics->add_GamepadAdded(Make<GamepadAddedListener>(mChanged.get()).Get(), &mAddedToken));

        ThrowIfFailed(mStatics->add_GamepadRemoved(Make<GamepadRemovedListener>(mChanged.get()).Get(), &mRemovedToken));

        ThrowIfFailed(mStaticsCtrl->add_ControllerPairingChanged(Make<UserPairingListener>().Get(), &mUserParingToken));

        ScanGamePads();
    }

    ~Impl()
    {
        if (mStatics)
        {
            (void)mStatics->remove_GamepadAdded(mAddedToken);
            mAddedToken.value = 0;

            (void)mStatics->remove_GamepadRemoved(mRemovedToken);
            mRemovedToken.value = 0;

            mStatics.Reset();
        }

        if (mStaticsCtrl)
        {
            (void)mStaticsCtrl->remove_ControllerPairingChanged(mUserParingToken);
            mUserParingToken.value = 0;

            mStaticsCtrl.Reset();
        }

        s_gamePad = nullptr;
    }

    void GetState(int player, _Out_ State& state, DeadZone deadZoneMode)
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::Xbox::Input;

        if (WaitForSingleObjectEx(mChanged.get(), 0, FALSE) == WAIT_OBJECT_0)
        {
            ScanGamePads();
        }

        if (player == -1)
            player = mMostRecentGamepad;

        if ((player >= 0) && (player < MAX_PLAYER_COUNT))
        {
            if (mGamePad[player])
            {
                RawGamepadReading reading;
                HRESULT hr = mGamePad[player]->GetRawCurrentReading(&reading);
                if (SUCCEEDED(hr))
                {
                    state.connected = true;
                    state.packet = reading.Timestamp;

                    state.buttons.a = (reading.Buttons & GamepadButtons::GamepadButtons_A) != 0;
                    state.buttons.b = (reading.Buttons & GamepadButtons::GamepadButtons_B) != 0;
                    state.buttons.x = (reading.Buttons & GamepadButtons::GamepadButtons_X) != 0;
                    state.buttons.y = (reading.Buttons & GamepadButtons::GamepadButtons_Y) != 0;

                    state.buttons.leftStick = (reading.Buttons & GamepadButtons::GamepadButtons_LeftThumbstick) != 0;
                    state.buttons.rightStick = (reading.Buttons & GamepadButtons::GamepadButtons_RightThumbstick) != 0;

                    state.buttons.leftShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_LeftShoulder) != 0;
                    state.buttons.rightShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_RightShoulder) != 0;

                    state.buttons.back = (reading.Buttons & GamepadButtons::GamepadButtons_View) != 0;
                    state.buttons.start = (reading.Buttons & GamepadButtons::GamepadButtons_Menu) != 0;

                    state.dpad.up = (reading.Buttons & GamepadButtons::GamepadButtons_DPadUp) != 0;
                    state.dpad.down = (reading.Buttons & GamepadButtons::GamepadButtons_DPadDown) != 0;
                    state.dpad.right = (reading.Buttons & GamepadButtons::GamepadButtons_DPadRight) != 0;
                    state.dpad.left = (reading.Buttons & GamepadButtons::GamepadButtons_DPadLeft) != 0;

                    ApplyStickDeadZone(reading.LeftThumbstickX, reading.LeftThumbstickY,
                                       deadZoneMode, 1.f, c_XboxOneThumbDeadZone,
                                       state.thumbSticks.leftX, state.thumbSticks.leftY);

                    ApplyStickDeadZone(reading.RightThumbstickX, reading.RightThumbstickY,
                                       deadZoneMode, 1.f, c_XboxOneThumbDeadZone,
                                       state.thumbSticks.rightX, state.thumbSticks.rightY);

                    state.triggers.left = reading.LeftTrigger;
                    state.triggers.right = reading.RightTrigger;

                    return;
                }
            }
        }

        memset(&state, 0, sizeof(State));
    }

    void GetCapabilities(int player, _Out_ Capabilities& caps)
    {
        using namespace Microsoft::WRL;
        using namespace ABI::Windows::Xbox::Input;

        if (WaitForSingleObjectEx(mChanged.get(), 0, FALSE) == WAIT_OBJECT_0)
        {
            ScanGamePads();
        }

        if (player == -1)
            player = mMostRecentGamepad;

        if ((player >= 0) && (player < MAX_PLAYER_COUNT))
        {
            if (mGamePad[player])
            {
                caps.connected = true;
                caps.gamepadType = Capabilities::UNKNOWN;

                ComPtr<IController> ctrl;
                HRESULT hr = mGamePad[player].As(&ctrl);
                if (SUCCEEDED(hr) && ctrl)
                {
                    hr = ctrl->get_Id(&caps.id);
                    if (FAILED(hr))
                        caps.id = 0;

                    Wrappers::HString str;
                    hr = ctrl->get_Type(str.GetAddressOf());
                    if (SUCCEEDED(hr))
                    {
                        const wchar_t* typeStr = str.GetRawBuffer(nullptr);
                        if (_wcsicmp(typeStr, L"Windows.Xbox.Input.Gamepad") == 0)
                        {
                            caps.gamepadType = Capabilities::GAMEPAD;
                        }
                        else if (_wcsicmp(typeStr, L"Microsoft.Xbox.Input.ArcadeStick") == 0)
                        {
                            caps.gamepadType = Capabilities::ARCADE_STICK;
                        }
                        else if (_wcsicmp(typeStr, L"Microsoft.Xbox.Input.Wheel") == 0)
                        {
                            caps.gamepadType = Capabilities::WHEEL;
                        }
                    }
                }
                else
                    caps.id = 0;

                return;
            }
        }

        memset(&caps, 0, sizeof(Capabilities));
    }

    bool SetVibration(int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger)
    {
        using namespace ABI::Windows::Xbox::Input;

        if (player == -1)
            player = mMostRecentGamepad;

        if ((player >= 0) && (player < MAX_PLAYER_COUNT))
        {
            if (mGamePad[player])
            {
                HRESULT hr;
                try
                {
                    GamepadVibration vib;
                    vib.LeftMotorLevel = leftMotor;
                    vib.RightMotorLevel = rightMotor;
                    vib.LeftTriggerLevel = leftTrigger;
                    vib.RightTriggerLevel = rightTrigger;
                    hr = mGamePad[player]->SetVibration(vib);
                }
                catch (...)
                {
                    // Handle case where gamepad might be invalid
                    hr = E_FAIL;
                }

                if (SUCCEEDED(hr))
                    return true;
            }
        }

        return false;
    }

    void Suspend()
    {
        for (size_t j = 0; j < MAX_PLAYER_COUNT; ++j)
        {
            mGamePad[j].Reset();
        }
    }

    void Resume()
    {
        // Make sure we rescan gamepads
        SetEvent(mChanged.get());
    }

    GamePad*    mOwner;

    static GamePad::Impl* s_gamePad;

    HANDLE mCtrlChanged;
    HANDLE mUserChanged;

private:
    int mMostRecentGamepad;

    void ScanGamePads()
    {
        using namespace ABI::Windows::Foundation::Collections;
        using namespace ABI::Windows::Xbox::Input;

        ComPtr<IVectorView<IGamepad*>> pads;
        ThrowIfFailed(mStatics->get_Gamepads(pads.GetAddressOf()));

        unsigned int count = 0;
        ThrowIfFailed(pads->get_Size(&count));

        // Check for removed gamepads
        for (size_t j = 0; j < MAX_PLAYER_COUNT; ++j)
        {
            if (mGamePad[j])
            {
                unsigned int k = 0;
                for (; k < count; ++k)
                {
                    ComPtr<IGamepad> pad;
                    HRESULT hr = pads->GetAt(k, pad.GetAddressOf());
                    if (SUCCEEDED(hr) && (pad == mGamePad[j]))
                    {
                        break;
                    }
                }

                if (k >= count)
                {
                    mGamePad[j].Reset();
                }
            }
        }

        // Check for added gamepads
        for (unsigned int j = 0; j < count; ++j)
        {
            ComPtr<IGamepad> pad;
            HRESULT hr = pads->GetAt(j, pad.GetAddressOf());
            if (SUCCEEDED(hr))
            {
                size_t empty = MAX_PLAYER_COUNT;
                size_t k = 0;
                for (; k < MAX_PLAYER_COUNT; ++k)
                {
                    if (mGamePad[k] == pad)
                    {
                        if (!j)
                            mMostRecentGamepad = static_cast<int>(k);
                        break;
                    }
                    else if (!mGamePad[k])
                    {
                        if (empty >= MAX_PLAYER_COUNT)
                            empty = k;
                    }
                }

                if (k >= MAX_PLAYER_COUNT)
                {
                    if (empty >= MAX_PLAYER_COUNT)
                    {
                        throw std::exception("Too many gamepads found");
                    }

                    mGamePad[empty] = pad;
                    if (!j)
                        mMostRecentGamepad = static_cast<int>(empty);
                }
            }
        }
    }

    ComPtr<ABI::Windows::Xbox::Input::IGamepadStatics> mStatics;
    ComPtr<ABI::Windows::Xbox::Input::IControllerStatics> mStaticsCtrl;
    ComPtr<ABI::Windows::Xbox::Input::IGamepad> mGamePad[MAX_PLAYER_COUNT];

    EventRegistrationToken mAddedToken;
    EventRegistrationToken mRemovedToken;
    EventRegistrationToken mUserParingToken;

    ScopedHandle mChanged;
};

GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;

#else

// XInput

#include <xinput.h>

static_assert(GamePad::MAX_PLAYER_COUNT == XUSER_MAX_COUNT, "xinput.h mismatch");

class GamePad::Impl
{
public:
    Impl(GamePad* owner) :
        mOwner(owner),
        mConnected{},
        mLastReadTime{}
    #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
        , mLeftMotor{}
        , mRightMotor{}
        , mSuspended(false)
    #endif
    {
        for (int j = 0; j < XUSER_MAX_COUNT; ++j)
        {
            ClearSlot(j, 0);
        }

        if (s_gamePad)
        {
            throw std::exception("GamePad is a singleton");
        }

        s_gamePad = this;
    }

    ~Impl()
    {
        s_gamePad = nullptr;
    }

    void GetState(int player, _Out_ State& state, DeadZone deadZoneMode)
    {
        if (player == -1)
            player = GetMostRecent();

        ULONGLONG time = GetTickCount64();

        if (!ThrottleRetry(player, time))
        {
        #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
            if (mSuspended)
            {
                memset(&state, 0, sizeof(State));
                state.connected = mConnected[player];
                return;
            }
        #endif

            XINPUT_STATE xstate;
            DWORD result = XInputGetState(DWORD(player), &xstate);
            if (result == ERROR_DEVICE_NOT_CONNECTED)
            {
                ClearSlot(player, time);
            }
            else
            {
                if (!mConnected[player])
                    mLastReadTime[player] = time;

                mConnected[player] = true;

                state.connected = true;
                state.packet = xstate.dwPacketNumber;

                WORD xbuttons = xstate.Gamepad.wButtons;
                state.buttons.a = (xbuttons & XINPUT_GAMEPAD_A) != 0;
                state.buttons.b = (xbuttons & XINPUT_GAMEPAD_B) != 0;
                state.buttons.x = (xbuttons & XINPUT_GAMEPAD_X) != 0;
                state.buttons.y = (xbuttons & XINPUT_GAMEPAD_Y) != 0;
                state.buttons.leftStick = (xbuttons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
                state.buttons.rightStick = (xbuttons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
                state.buttons.leftShoulder = (xbuttons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
                state.buttons.rightShoulder = (xbuttons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
                state.buttons.back = (xbuttons & XINPUT_GAMEPAD_BACK) != 0;
                state.buttons.start = (xbuttons & XINPUT_GAMEPAD_START) != 0;

                state.dpad.up = (xbuttons & XINPUT_GAMEPAD_DPAD_UP) != 0;
                state.dpad.down = (xbuttons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
                state.dpad.right = (xbuttons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
                state.dpad.left = (xbuttons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;

                if (deadZoneMode == DEAD_ZONE_NONE)
                {
                    state.triggers.left = ApplyLinearDeadZone(float(xstate.Gamepad.bLeftTrigger), 255.f, 0.f);
                    state.triggers.right = ApplyLinearDeadZone(float(xstate.Gamepad.bRightTrigger), 255.f, 0.f);
                }
                else
                {
                    state.triggers.left = ApplyLinearDeadZone(float(xstate.Gamepad.bLeftTrigger), 255.f, float(XINPUT_GAMEPAD_TRIGGER_THRESHOLD));
                    state.triggers.right = ApplyLinearDeadZone(float(xstate.Gamepad.bRightTrigger), 255.f, float(XINPUT_GAMEPAD_TRIGGER_THRESHOLD));
                }

                ApplyStickDeadZone(float(xstate.Gamepad.sThumbLX), float(xstate.Gamepad.sThumbLY),
                                   deadZoneMode, 32767.f, float(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE),
                                   state.thumbSticks.leftX, state.thumbSticks.leftY);

                ApplyStickDeadZone(float(xstate.Gamepad.sThumbRX), float(xstate.Gamepad.sThumbRY),
                                   deadZoneMode, 32767.f, float(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE),
                                   state.thumbSticks.rightX, state.thumbSticks.rightY);

                return;
            }
        }

        memset(&state, 0, sizeof(State));
    }

    void GetCapabilities(int player, _Out_ Capabilities& caps)
    {
        if (player == -1)
            player = GetMostRecent();

        ULONGLONG time = GetTickCount64();

        if (!ThrottleRetry(player, time))
        {
            XINPUT_CAPABILITIES xcaps;
            DWORD result = XInputGetCapabilities(DWORD(player), 0, &xcaps);
            if (result == ERROR_DEVICE_NOT_CONNECTED)
            {
                ClearSlot(player, time);
            }
            else
            {
                if (!mConnected[player])
                    mLastReadTime[player] = time;

                mConnected[player] = true;

                caps.connected = true;
                caps.id = uint64_t(player);
                if (xcaps.Type == XINPUT_DEVTYPE_GAMEPAD)
                {
                    static_assert(Capabilities::GAMEPAD == XINPUT_DEVSUBTYPE_GAMEPAD, "xinput.h mismatch");
                #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
                    static_assert(XINPUT_DEVSUBTYPE_WHEEL == Capabilities::WHEEL, "xinput.h mismatch");
                    static_assert(XINPUT_DEVSUBTYPE_ARCADE_STICK == Capabilities::ARCADE_STICK, "xinput.h mismatch");
                    static_assert(XINPUT_DEVSUBTYPE_FLIGHT_STICK == Capabilities::FLIGHT_STICK, "xinput.h mismatch");
                    static_assert(XINPUT_DEVSUBTYPE_DANCE_PAD == Capabilities::DANCE_PAD, "xinput.h mismatch");
                    static_assert(XINPUT_DEVSUBTYPE_GUITAR == Capabilities::GUITAR, "xinput.h mismatch");
                    static_assert(XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE == Capabilities::GUITAR_ALTERNATE, "xinput.h mismatch");
                    static_assert(XINPUT_DEVSUBTYPE_DRUM_KIT == Capabilities::DRUM_KIT, "xinput.h mismatch");
                    static_assert(XINPUT_DEVSUBTYPE_GUITAR_BASS == Capabilities::GUITAR_BASS, "xinput.h mismatch");
                    static_assert(XINPUT_DEVSUBTYPE_ARCADE_PAD == Capabilities::ARCADE_PAD, "xinput.h mismatch");
                #endif

                    caps.gamepadType = Capabilities::Type(xcaps.SubType);
                }

                return;
            }
        }

        memset(&caps, 0, sizeof(Capabilities));
    }

    bool SetVibration(int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger)
    {
        if (player == -1)
            player = GetMostRecent();

        ULONGLONG time = GetTickCount64();

        if (ThrottleRetry(player, time))
        {
            return false;
        }

        // XInput does not provide a way to set the left/right trigger impulse motors on the Xbox One Controller,
        // and these motors are not present on the Xbox 360 Common Controller
        UNREFERENCED_PARAMETER(leftTrigger);
        UNREFERENCED_PARAMETER(rightTrigger);

    #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
        mLeftMotor[player] = leftMotor;
        mRightMotor[player] = rightMotor;

        if (mSuspended)
            return mConnected[player];
    #endif

        XINPUT_VIBRATION xvibration;
        xvibration.wLeftMotorSpeed = WORD(leftMotor * 0xFFFF);
        xvibration.wRightMotorSpeed = WORD(rightMotor * 0xFFFF);
        DWORD result = XInputSetState(DWORD(player), &xvibration);
        if (result == ERROR_DEVICE_NOT_CONNECTED)
        {
            ClearSlot(player, time);
            return false;
        }
        else
        {
            if (!mConnected[player])
                mLastReadTime[player] = time;

            mConnected[player] = true;
            return (result == ERROR_SUCCESS);
        }
    }

    void Suspend()
    {
    #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
        XInputEnable(FALSE);
    #else
            // For XInput 9.1.0, we have to emulate the behavior of XInputEnable( FALSE )
        if (!mSuspended)
        {
            for (size_t j = 0; j < XUSER_MAX_COUNT; ++j)
            {
                if (mConnected[j])
                {
                    XINPUT_VIBRATION xvibration;
                    xvibration.wLeftMotorSpeed = xvibration.wRightMotorSpeed = 0;
                    (void)XInputSetState(DWORD(j), &xvibration);
                }
            }

            mSuspended = true;
        }
    #endif
    }

    void Resume()
    {
    #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
        XInputEnable(TRUE);
    #else
            // For XInput 9.1.0, we have to emulate the behavior of XInputEnable( TRUE )
        if (mSuspended)
        {
            ULONGLONG time = GetTickCount64();

            for (int j = 0; j < XUSER_MAX_COUNT; ++j)
            {
                if (mConnected[j])
                {
                    XINPUT_VIBRATION xvibration;
                    xvibration.wLeftMotorSpeed = WORD(mLeftMotor[j] * 0xFFFF);
                    xvibration.wRightMotorSpeed = WORD(mRightMotor[j] * 0xFFFF);
                    DWORD result = XInputSetState(DWORD(j), &xvibration);
                    if (result == ERROR_DEVICE_NOT_CONNECTED)
                    {
                        ClearSlot(j, time);
                    }
                }
            }

            mSuspended = false;
        }
    #endif
    }

    GamePad*    mOwner;

    static GamePad::Impl* s_gamePad;

private:
    bool        mConnected[XUSER_MAX_COUNT];
    ULONGLONG   mLastReadTime[XUSER_MAX_COUNT];

#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
    // Variables for emulating XInputEnable on XInput 9.1.0
    float       mLeftMotor[XUSER_MAX_COUNT];
    float       mRightMotor[XUSER_MAX_COUNT];
    bool        mSuspended;
#endif

    bool ThrottleRetry(int player, ULONGLONG time)
    {
        // This function minimizes a potential performance issue with XInput on Windows when
        // checking a disconnected controller slot which requires device enumeration.
        // This throttling keeps checks for newly connected gamepads to about once a second

        if ((player < 0) || (player >= XUSER_MAX_COUNT))
            return true;

        if (mConnected[player])
            return false;

        for (int j = 0; j < XUSER_MAX_COUNT; ++j)
        {
            if (!mConnected[j])
            {
                LONGLONG delta = time - mLastReadTime[j];

                LONGLONG interval = 1000;
                if (j != player)
                    interval /= 4;

                if ((delta >= 0) && (delta < interval))
                    return true;
            }
        }

        return false;
    }

    void ClearSlot(int player, ULONGLONG time)
    {
        mConnected[player] = false;
        mLastReadTime[player] = time;
    #if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
        mLeftMotor[player] = mRightMotor[player] = 0.f;
    #endif
    }

    int GetMostRecent()
    {
        int player = -1;
        ULONGLONG time = 0;

        for (size_t j = 0; j < XUSER_MAX_COUNT; ++j)
        {
            if (mConnected[j] && (mLastReadTime[j] > time))
            {
                time = mLastReadTime[j];
                player = static_cast<int>(j);
            }
        }

        return player;
    }
};

GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;

#endif

#pragma warning( disable : 4355 )

GamePad::GamePad() noexcept(false)
    : pImpl(std::make_unique<Impl>(this))
{
}

GamePad::GamePad(GamePad&& moveFrom) noexcept
    : pImpl(std::move(moveFrom.pImpl))
{
    pImpl->mOwner = this;
}

// Move assignment.
GamePad& GamePad::operator= (GamePad&& moveFrom) noexcept
{
    pImpl = std::move(moveFrom.pImpl);
    pImpl->mOwner = this;
    return *this;
}

// Public destructor.

GamePad::State GamePad::GetState(int player, DeadZone deadZoneMode)
{
    State state;
    pImpl->GetState(player, state, deadZoneMode);
    return state;
}

GamePad::Capabilities GamePad::GetCapabilities(int player)
{
    Capabilities caps;
    pImpl->GetCapabilities(player, caps);
    return caps;
}

bool GamePad::SetVibration(int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger)
{
    return pImpl->SetVibration(player, leftMotor, rightMotor, leftTrigger, rightTrigger);
}

void GamePad::Suspend()
{
    pImpl->Suspend();
}

void GamePad::Resume()
{
    pImpl->Resume();
}

#if (_WIN32_WINNT >= 0x0A00 /*_WIN32_WINNT_WIN10*/ ) || defined(_XBOX_ONE)
void GamePad::RegisterEvents(HANDLE ctrlChanged, HANDLE userChanged)
{
    pImpl->mCtrlChanged = (!ctrlChanged) ? INVALID_HANDLE_VALUE : ctrlChanged;
    pImpl->mUserChanged = (!userChanged) ? INVALID_HANDLE_VALUE : userChanged;
}
#endif

GamePad& GamePad::Get()
{
    if (!Impl::s_gamePad || !Impl::s_gamePad->mOwner)
        throw std::exception("GamePad is a singleton");

    return *Impl::s_gamePad->mOwner;
}

// ButtonStateTracker

#define UPDATE_BUTTON_STATE(field) field = static_cast<ButtonState>( ( !!state.buttons.field ) | ( ( !!state.buttons.field ^ !!lastState.buttons.field ) << 1 ) );

void GamePad::ButtonStateTracker::Update(const GamePad::State& state)
{
    UPDATE_BUTTON_STATE(a);

    assert((!state.buttons.a && !lastState.buttons.a) == (a == UP));
    assert((state.buttons.a && lastState.buttons.a) == (a == HELD));
    assert((!state.buttons.a && lastState.buttons.a) == (a == RELEASED));
    assert((state.buttons.a && !lastState.buttons.a) == (a == PRESSED));

    UPDATE_BUTTON_STATE(b);
    UPDATE_BUTTON_STATE(x);
    UPDATE_BUTTON_STATE(y);

    UPDATE_BUTTON_STATE(leftStick);
    UPDATE_BUTTON_STATE(rightStick);

    UPDATE_BUTTON_STATE(leftShoulder);
    UPDATE_BUTTON_STATE(rightShoulder);

    UPDATE_BUTTON_STATE(back);
    UPDATE_BUTTON_STATE(start);

    dpadUp = static_cast<ButtonState>((!!state.dpad.up) | ((!!state.dpad.up ^ !!lastState.dpad.up) << 1));
    dpadDown = static_cast<ButtonState>((!!state.dpad.down) | ((!!state.dpad.down ^ !!lastState.dpad.down) << 1));
    dpadLeft = static_cast<ButtonState>((!!state.dpad.left) | ((!!state.dpad.left ^ !!lastState.dpad.left) << 1));
    dpadRight = static_cast<ButtonState>((!!state.dpad.right) | ((!!state.dpad.right ^ !!lastState.dpad.right) << 1));

    assert((!state.dpad.up && !lastState.dpad.up) == (dpadUp == UP));
    assert((state.dpad.up && lastState.dpad.up) == (dpadUp == HELD));
    assert((!state.dpad.up && lastState.dpad.up) == (dpadUp == RELEASED));
    assert((state.dpad.up && !lastState.dpad.up) == (dpadUp == PRESSED));

    // Handle 'threshold' tests which emulate buttons

    bool threshold = state.IsLeftThumbStickUp();
    leftStickUp = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickUp()) << 1));

    threshold = state.IsLeftThumbStickDown();
    leftStickDown = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickDown()) << 1));

    threshold = state.IsLeftThumbStickLeft();
    leftStickLeft = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickLeft()) << 1));

    threshold = state.IsLeftThumbStickRight();
    leftStickRight = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickRight()) << 1));

    threshold = state.IsRightThumbStickUp();
    rightStickUp = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickUp()) << 1));

    threshold = state.IsRightThumbStickDown();
    rightStickDown = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickDown()) << 1));

    threshold = state.IsRightThumbStickLeft();
    rightStickLeft = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickLeft()) << 1));

    threshold = state.IsRightThumbStickRight();
    rightStickRight = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickRight()) << 1));

    threshold = state.IsLeftTriggerPressed();
    leftTrigger = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsLeftTriggerPressed()) << 1));

    threshold = state.IsRightTriggerPressed();
    rightTrigger = static_cast<ButtonState>((!!threshold) | ((!!threshold ^ !!lastState.IsRightTriggerPressed()) << 1));

    lastState = state;
}

#undef UPDATE_BUTTON_STATE

void GamePad::ButtonStateTracker::Reset() noexcept
{
    memset(this, 0, sizeof(ButtonStateTracker));
}


#include "MainApplication.hpp"

extern lang::Language language;

namespace ui
{
    extern MainApplication *mainapp;

{
        this->bground = pu::ui::elm::Rectangle::New(0, 0, 1280, 720, pu::ui::Color(0,102,153,255));
        this->Add(this->bground);
        this->bootText = pu::ui::elm::TextBlock::New(640, 360, utils::replace(lang::GetLabel(lang::Label::BOOT_LOADING),"{{TEST}}","FUNZIONA"), 40);
        this->bootText->SetColor(pu::ui::Color(255,255,255,255));
        this->bootText->SetHorizontalAlign(pu::ui::elm::HorizontalAlign::Center);
        this->Add(this->bootText);
        this->progressBar = pu::ui::elm::ProgressBar::New(320,420,640,10,100.0f);
        this->progressBar->SetColor(pu::ui::Color(0,51,102,255));
        this->progressBar->SetProgressColor(pu::ui::Color(255,255,255,255));
        this->progressBar->SetVisible(true);
        this->Add(this->progressBar);
    }

	void BootLayout::SetText(std::string Text)
    {
        this->bootText->SetText(Text);
    }

	void BootLayout::SetProgress(float Progress)
    {
        this->progressBar->SetProgress(Progress);
    }
}


{
        this->bootText->SetText(Text);
    }

#include "MainApplication.hpp"

extern lang::Language language;

namespace ui
{
    extern MainApplication *mainapp;

    BootLayout::BootLayout()
    {
        this->bground = pu::ui::elm::Rectangle::New(0, 0, 1280, 720, pu::ui::Color(0,102,153,255));
        this->Add(this->bground);
        this->bootText = pu::ui::elm::TextBlock::New(640, 360, utils::replace(lang::GetLabel(lang::Label::BOOT_LOADING),"{{TEST}}","FUNZIONA"), 40);
        this->bootText->SetColor(pu::ui::Color(255,255,255,255));
        this->bootText->SetHorizontalAlign(pu::ui::elm::HorizontalAlign::Center);
        this->Add(this->bootText);
        this->progressBar = pu::ui::elm::ProgressBar::New(320,420,640,10,100.0f);
        this->progressBar->SetColor(pu::ui::Color(0,51,102,255));
        this->progressBar->SetProgressColor(pu::ui::Color(255,255,255,255));
        this->progressBar->SetVisible(true);
        this->Add(this->progressBar);
    }

	void BootLayout::SetText(std::string Text)

	void BootLayout::SetProgress(float Progress)
    {
        this->progressBar->SetProgress(Progress);
    }
}


#include "MainApplication.hpp"

extern lang::Language language;

namespace ui
{
    extern MainApplication *mainapp;

    BootLayout::BootLayout()
    {
        this->bground = pu::ui::elm::Rectangle::New(0, 0, 1280, 720, pu::ui::Color(0,102,153,255));
        this->Add(this->bgrou

        this->bootText = pu::ui::elm::TextBlock::New(640, 360, utils::replace(lang::GetLabel(lang::Label::BOOT_LOADING),"{{TEST}}","FUNZIONA"), 40);
        this->bootText->SetColor(pu::ui::Color(255,255,255,255));
        this->bootText->SetHorizontalAlign(pu::ui::elm::HorizontalAlign::Center);
        this->Add(this->bootText);
        this->progressBar = pu::ui::elm::ProgressBar::New(320,420,640,10,100.0f);
        this->progressBar->SetColor(pu::ui::Color(0,51,102,255));
        this->progressBar->SetProgressColor(pu::ui::Color(255,255,255,255));
        this->progressBar->SetVisible(true);
        this->Add(this->progressBar);
    }

	void BootLayout::SetText(std::string Text)
    {
        this->bootText->SetText(Text);
    }

	void BootLayout::SetProgress(float Progress)
    {
        this->progressBar->SetProgress(Progress);
    }
}


/*============================================================================

The Medical Imaging Interaction Toolkit (MITK)

All rights reserved.

============================================================================*/

// MITK
#include "mitkNavigationDataSetWriterXML.h"
#include <mitkIGTMimeTypes.h>
#include <mitkLocaleSwitch.h>

// Third Party
#include <tinyxml2.h>
#include <itksys/SystemTools.hxx>
#include <fstream>
#include <iostream>

mitk::NavigationDataSetWriterXML::NavigationDataSetWriterXML() : AbstractFileWriter(NavigationDataSet::GetStaticNameOfClass(),
  mitk::IGTMimeTypes::NAVIGATIONDATASETXML_MIMETYPE(),
  "MITK NavigationDataSet Writer (XML)")
{
  RegisterService();
}

mitk::NavigationDataSetWriterXML::NavigationDataSetWriterXML(const mitk::NavigationDataSetWriterXML& other) : AbstractFileWriter(other)
{
}

mitk::NavigationDataSetWriterXML::~NavigationDataSetWriterXML()
{
}

{
  return new NavigationDataSetWriterXML(*this);
}

void mitk::NavigationDataSetWriterXML::Write()
{
  std::ostream* out = GetOutputStream();
  if (out == nullptr)
  {
    out = new std::ofstream( GetOutputLocation().c_str() );
  }
  mitk::NavigationDataSet::ConstPointer data = dynamic_cast<const NavigationDataSet*> (this->GetInput());

  mitk::LocaleSwitch localeSwitch("C");

  StreamHeader(out, data);
  StreamData(out, data);
  StreamFooter(out);

  // Cleanup
  out->flush();
  delete out;
}

void mitk::NavigationDataSetWriterXML::StreamHeader (std::ostream* stream, mitk::NavigationDataSet::ConstPointer data)
{
  stream->precision(10);

  //TODO store date and GMT time
  //checking if the stream is good
  if (stream->good())
  {
    *stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" << std::endl;
    /**m_Stream << "<Version Ver=\"1\" />" << std::endl;*/
    // should be a generic version, meaning a member variable, which has the actual version
    *stream << "    " << "<Data ToolCount=\"" << data->GetNumberOfTools() << "\" version=\"1.0\">" << std::endl;
  }
}

void mitk::NavigationDataSetWriterXML::StreamData (std::ostream* stream, mitk::NavigationDataSet::ConstPointer data)
{
  // For each time step in the Dataset
  for (auto it = data->Begin(); it != data->End(); it++)
  {
    for (std::size_t toolIndex = 0; toolIndex < it->size(); toolIndex++)
    {
      mitk::NavigationData::Pointer nd = it->at(toolIndex);
      tinyxml2::XMLDocument doc;
      auto *elem = doc.NewElement("ND");

      elem->SetAttribute("Time", nd->GetIGTTimeStamp());
      elem->SetAttribute("Tool", static_cast<int>(toolIndex));
      elem->SetAttribute("X", nd->GetPosition()[0]);
      elem->SetAttribute("Y", nd->GetPosition()[1]);
      elem->SetAttribute("Z", nd->GetPosition()[2]);

      elem->SetAttribute("QX", nd->GetOrientation()[0]);
      elem->SetAttribute("QY", nd->GetOrientation()[1]);
      elem->SetAttribute("QZ", nd->GetOrientation()[2]);
      elem->SetAttribute("QR", nd->GetOrientation()[3]);

      elem->SetAttribute("C00", nd->GetCovErrorMatrix()[0][0]);
      elem->SetAttribute("C01", nd->GetCovErrorMatrix()[0][1]);
      elem->SetAttribute("C02", nd->GetCovErrorMatrix()[0][2]);
      elem->SetAttribute("C03", nd->GetCovErrorMatrix()[0][3]);
      elem->SetAttribute("C04", nd->GetCovErrorMatrix()[0][4]);
      elem->SetAttribute("C05", nd->GetCovErrorMatrix()[0][5]);
      elem->SetAttribute("C10", nd->GetCovErrorMatrix()[1][0]);
      elem->SetAttribute("C11", nd->GetCovErrorMatrix()[1][1]);
      elem->SetAttribute("C12", nd->GetCovErrorMatrix()[1][2]);
      elem->SetAttribute("C13", nd->GetCovErrorMatrix()[1][3]);
      elem->SetAttribute("C14", nd->GetCovErrorMatrix()[1][4]);
      elem->SetAttribute("C15", nd->GetCovErrorMatrix()[1][5]);

      if (nd->IsDataValid())
        elem->SetAttribute("Valid",1);
      else
        elem->SetAttribute("Valid",0);

      if (nd->GetHasOrientation())
        elem->SetAttribute("hO",1);
      else
        elem->SetAttribute("hO",0);

      if (nd->GetHasPosition())
        elem->SetAttribute("hP",1);
      else
        elem->SetAttribute("hP",0);

      tinyxml2::XMLPrinter printer;
      doc.Print(&printer);

      *stream << "        " << printer.CStr() << std::endl;
    }
  }
}

void mitk::NavigationDataSetWriterXML::StreamFooter (std::ostream* stream)
{
  *stream << "</Data>" << std::endl;
}


{
  stream->precision(10);

  //TODO store date and GMT time
  //checking if the stream is good
  if (stream->good())
  {
    *stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" << std::endl;
    /**m_Stream << "<Version Ver=\"1\" />" << std::endl;*/
    // should be a generic version, meaning a member variable, which has the actual version
    *stream << "    " << "<Data ToolCount=\"" << data->GetNumberOfTools() << "\" version=\"1.0\">" << std::endl;
  }
}

/*============================================================================

The Medical Imaging Interaction Toolkit (MITK)

All rights reserved.

============================================================================*/

// MITK
#include "mitkNavigationDataSetWriterXML.h"
#include <mitkIGTMimeTypes.h>
#include <mitkLocaleSwitch.h>

// Third Party
#include <tinyxml2.h>
#include <itksys/SystemTools.hxx>
#include <fstream>
#include <iostream>

mitk::NavigationDataSetWriterXML::NavigationDataSetWriterXML() : AbstractFileWriter(NavigationDataSet::GetStaticNameOfClass(),
  mitk::IGTMimeTypes::NAVIGATIONDATASETXML_MIMETYPE(),
  "MITK NavigationDataSet Writer (XML)")
{
  RegisterService();
}

mitk::NavigationDataSetWriterXML::NavigationDataSetWriterXML(const mitk::NavigationDataSetWriterXML& other) : AbstractFileWriter(other)
{
}

mitk::NavigationDataSetWriterXML::~NavigationDataSetWriterXML()
{
}

mitk::NavigationDataSetWriterXML* mitk::NavigationDataSetWriterXML::Clone() const
{
  return new NavigationDataSetWriterXML(*this);
}

void mitk::NavigationDataSetWriterXML::Write()
{
  std::ostream* out = GetOutputStream();
  if (out == nullptr)
  {
    out = new std::ofstream( GetOutputLocation().c_str() );
  }
  mitk::NavigationDataSet::ConstPointer data = dynamic_cast<const NavigationDataSet*> (this->GetInput());

  mitk::LocaleSwitch localeSwitch("C");

  StreamHeader(out, data);
  StreamData(out, data);
  StreamFooter(out);

  // Cleanup
  out->flush();
  delete out;
}

void mitk::NavigationDataSetWriterXML::StreamHeader (std::ostream* stream, mitk::NavigationDataSet::ConstPointer data)

void mitk::NavigationDataSetWriterXML::StreamData (std::ostream* stream, mitk::NavigationDataSet::ConstPointer data)
{
  // For each time step in the Dataset
  for (auto it = data->Begin(); it != data->End(); it++)
  {
    for (std::size_t toolIndex = 0; toolIndex < it->size(); toolIndex++)
    {
      mitk::NavigationData::Pointer nd = it->at(toolIndex);
      tinyxml2::XMLDocument doc;
      auto *elem = doc.NewElement("ND");

      elem->SetAttribute("Time", nd->GetIGTTimeStamp());
      elem->SetAttribute("Tool", static_cast<int>(toolIndex));
      elem->SetAttribute("X", nd->GetPosition()[0]);
      elem->SetAttribute("Y", nd->GetPosition()[1]);
      elem->SetAttribute("Z", nd->GetPosition()[2]);

      elem->SetAttribute("QX", nd->GetOrientation()[0]);
      elem->SetAttribute("QY", nd->GetOrientation()[1]);
      elem->SetAttribute("QZ", nd->GetOrientation()[2]);
      elem->SetAttribute("QR", nd->GetOrientation()[3]);

      elem->SetAttribute("C00", nd->GetCovErrorMatrix()[0][0]);
      elem->SetAttribute("C01", nd->GetCovErrorMatrix()[0][1]);
      elem->SetAttribute("C02", nd->GetCovErrorMatrix()[0][2]);
      elem->SetAttribute("C03", nd->GetCovErrorMatrix()[0][3]);
      elem->SetAttribute("C04", nd->GetCovErrorMatrix()[0][4]);
      elem->SetAttribute("C05", nd->GetCovErrorMatrix()[0][5]);
      elem->SetAttribute("C10", nd->GetCovErrorMatrix()[1][0]);
      elem->SetAttribute("C11", nd->GetCovErrorMatrix()[1][1]);
      elem->SetAttribute("C12", nd->GetCovErrorMatrix()[1][2]);
      elem->SetAttribute("C13", nd->GetCovErrorMatrix()[1][3]);
      elem->SetAttribute("C14", nd->GetCovErrorMatrix()[1][4]);
      elem->SetAttribute("C15", nd->GetCovErrorMatrix()[1][5]);

      if (nd->IsDataValid())
        elem->SetAttribute("Valid",1);
      else
        elem->SetAttribute("Valid",0);

      if (nd->GetHasOrientation())
        elem->SetAttribute("hO",1);
      else
        elem->SetAttribute("hO",0);

      if (nd->GetHasPosition())
        elem->SetAttribute("hP",1);
      else
        elem->SetAttribute("hP",0);

      tinyxml2::XMLPrinter printer;
      doc.Print(&printer);

      *stream << "        " << printer.CStr() << std::endl;
    }
  }
}

void mitk::NavigationDataSetWriterXML::StreamFooter (std::ostream* stream)
{
  *stream << "</Data>" << std::endl;
}


/*============================================================================

The Medical Imaging Interaction Toolkit (MITK)

All rights reserved.

============================================================================*/

// MITK
#include "mitkNavigationDataSetWriterXML.h"
#include <mitkIGTMimeTypes.h>
#include <mitkLocaleSwitch.h>

// Third Party
#include <tinyxml2.h>
#include <itksys/SystemTools.hxx>
#include <fstream>
#include <iostream>

mitk::NavigationDataSetWriterXML::NavigationDataSetWriterXML() : AbstractFileWriter(NavigationDataSet::GetStaticNameOfClass(),
  mitk::IGTMimeTypes::NAVIGATIONDATASETXML_MIMETYPE(),
  "MITK NavigationDataSet Writer (XML)")
{
  RegisterService();
}

mitk::NavigationDataSetWriterXML::NavigationDataSetWriterXML(const mitk::NavigationDataSetWriterXML& other) : AbstractFileWriter(other)
{
}

mitk::NavigationDataSetWriterXML::~NavigationDataSetWriterXML()
{
}

mitk::NavigationDataSetWriterXML* mitk::NavigationDataSetWriterXML::Clone() const
{
  return new Navigatio

}

void mitk::NavigationDataSetWriterXML::Write()
{
  std::ostream* out = GetOutputStream();
  if (out == nullptr)
  {
    out = new std::ofstream( GetOutputLocation().c_str() );
  }
  mitk::NavigationDataSet::ConstPointer data = dynamic_cast<const NavigationDataSet*> (this->GetInput());

  mitk::LocaleSwitch localeSwitch("C");

  StreamHeader(out, data);
  StreamData(out, data);
  StreamFooter(out);

  // Cleanup
  out->flush();
  delete out;
}

void mitk::NavigationDataSetWriterXML::StreamHeader (std::ostream* stream, mitk::NavigationDataSet::ConstPointer data)
{
  stream->precision(10);

  //TODO store date and GMT time
  //checking if the stream is good
  if (stream->good())
  {
    *stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" << std::endl;
    /**m_Stream << "<Version Ver=\"1\" />" << std::endl;*/
    // should be a generic version, meaning a member variable, which has the actual version
    *stream << "    " << "<Data ToolCount=\"" << data->GetNumberOfTools() << "\" version=\"1.0\">" << std::endl;
  }
}

void mitk::NavigationDataSetWriterXML::StreamData (std::ostream* stream, mitk::NavigationDataSet::ConstPointer data)
{
  // For each time step in the Dataset
  for (auto it = data->Begin(); it != data->End(); it++)
  {
    for (std::size_t toolIndex = 0; toolIndex < it->size(); toolIndex++)
    {
      mitk::NavigationData::Pointer nd = it->at(toolIndex);
      tinyxml2::XMLDocument doc;
      auto *elem = doc.NewElement("ND");

      elem->SetAttribute("Time", nd->GetIGTTimeStamp());
      elem->SetAttribute("Tool", static_cast<int>(toolIndex));
      elem->SetAttribute("X", nd->GetPosition()[0]);
      elem->SetAttribute("Y", nd->GetPosition()[1]);
      elem->SetAttribute("Z", nd->GetPosition()[2]);

      elem->SetAttribute("QX", nd->GetOrientation()[0]);
      elem->SetAttribute("QY", nd->GetOrientation()[1]);
      elem->SetAttribute("QZ", nd->GetOrientation()[2]);
      elem->SetAttribute("QR", nd->GetOrientation()[3]);

      elem->SetAttribute("C00", nd->GetCovErrorMatrix()[0][0]);
      elem->SetAttribute("C01", nd->GetCovErrorMatrix()[0][1]);
      elem->SetAttribute("C02", nd->GetCovErrorMatrix()[0][2]);
      elem->SetAttribute("C03", nd->GetCovErrorMatrix()[0][3]);
      elem->SetAttribute("C04", nd->GetCovErrorMatrix()[0][4]);
      elem->SetAttribute("C05", nd->GetCovErrorMatrix()[0][5]);
      elem->SetAttribute("C10", nd->GetCovErrorMatrix()[1][0]);
      elem->SetAttribute("C11", nd->GetCovErrorMatrix()[1][1]);
      elem->SetAttribute("C12", nd->GetCovErrorMatrix()[1][2]);
      elem->SetAttribute("C13", nd->GetCovErrorMatrix()[1][3]);
      elem->SetAttribute("C14", nd->GetCovErrorMatrix()[1][4]);
      elem->SetAttribute("C15", nd->GetCovErrorMatrix()[1][5]);

      if (nd->IsDataValid())
        elem->SetAttribute("Valid",1);
      else
        elem->SetAttribute("Valid",0);

      if (nd->GetHasOrientation())
        elem->SetAttribute("hO",1);
      else
        elem->SetAttribute("hO",0);

      if (nd->GetHasPosition())
        elem->SetAttribute("hP",1);
      else
        elem->SetAttribute("hP",0);

      tinyxml2::XMLPrinter printer;
      doc.Print(&printer);

      *stream << "        " << printer.CStr() << std::endl;
    }
  }
}

void mitk::NavigationDataSetWriterXML::StreamFooter (std::ostream* stream)
{
  *stream << "</Data>" << std::endl;
}


// This MFC Library source code supports the Microsoft Office Fluent User Interface
// (the "Fluent UI") and is provided only as referential material to supplement the
// Microsoft Foundation Classes Reference and related electronic documentation
// included with the MFC C++ library software.
// License terms to copy, use or distribute the Fluent UI are available separately.
// To learn more about our Fluent UI licensing program, please visit
// All rights reserved.

#include "stdafx.h"
#include "multimon.h"
#include "afxtooltipctrl.h"

#include "afxtoolbarimages.h"
#include "afxtoolbar.h"
#include "afxtoolbarbutton.h"
#include "afxdrawmanager.h"
#include "afxvisualmanager.h"
#include "afxoutlookbarpane.h"
#include "afxribbonbutton.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#ifndef TTS_BALLOON
#define TTS_BALLOON 0x40
#endif

#ifndef TTM_SETTITLE
#define TTM_SETTITLE (WM_USER + 32)
#endif

// CMFCToolTipCtrl

IMPLEMENT_DYNCREATE(CMFCToolTipCtrl, CToolTipCtrl)

CMFCToolTipCtrl::CMFCToolTipCtrl(CMFCToolTipInfo* pParams/* = NULL*/)
{
	SetParams(pParams);

	m_pToolBar = NULL;
	m_pToolBarImages = NULL;
	m_pHotButton = NULL;
	m_sizeImage = CSize(0, 0);
	m_ptMargin = CPoint(0, 0);
	m_ptLocation = CPoint(-1, -1);
	m_pRibbonButton = NULL;
	m_nRibbonImageType = 0;
	m_nFixedWidthRegular = 0;
	m_nFixedWidthWithImage = 0;
}

CMFCToolTipCtrl::~CMFCToolTipCtrl()
{
}

BEGIN_MESSAGE_MAP(CMFCToolTipCtrl, CToolTipCtrl)
	ON_WM_PAINT()
	ON_WM_CREATE()
	ON_WM_ERASEBKGND()
	ON_NOTIFY_REFLECT(TTN_SHOW, &CMFCToolTipCtrl::OnShow)
	ON_NOTIFY_REFLECT(TTN_POP, &CMFCToolTipCtrl::OnPop)
END_MESSAGE_MAP()

// CMFCToolTipCtrl message handlers

void CMFCToolTipCtrl::OnPaint()
{
	if (m_Params.m_bBalloonTooltip)
	{
		CToolTipCtrl::OnPaint();
		return;
	}

	CPaintDC dcPaint(this); // device context for painting

	CMemDC memDC (dcPaint, this);
	CDC* pDC = &memDC.GetDC ();

	CRect rect;
	GetClientRect(rect);

	CRect rectMargin;
	GetMargin(rectMargin);

	CRect rectText = rect;

	rectText.DeflateRect(rectMargin);
	rectText.DeflateRect(m_ptMargin.x, m_ptMargin.y);

	COLORREF clrLine = m_Params.m_clrBorder == (COLORREF)-1 ? ::GetSysColor(COLOR_INFOTEXT) : m_Params.m_clrBorder;
	COLORREF clrText = m_Params.m_clrText == (COLORREF)-1 ? ::GetSysColor(COLOR_INFOTEXT) : m_Params.m_clrText;

	// Fill background:
	OnFillBackground(pDC, rect, clrText, clrLine);

	CPen penLine(PS_SOLID, 1, clrLine);
	CPen* pOldPen = pDC->SelectObject(&penLine);

	// Draw border:
	OnDrawBorder(pDC, rect, clrLine);

	// Draw icon:
	if (m_sizeImage != CSize(0, 0) && m_Params.m_bDrawIcon)
	{
		CRect rectImage = rectText;
		rectImage.right = rectImage.left + m_sizeImage.cx;
		rectImage.bottom = rectImage.top + m_sizeImage.cy;

		OnDrawIcon(pDC, rectImage);

		rectText.left += m_sizeImage.cx + m_ptMargin.x;
	}

	pDC->SetBkMode(TRANSPARENT);
	pDC->SetTextColor(clrText);

	// Draw label:
	int nTextHeight = OnDrawLabel(pDC, rectText, FALSE).cy;

	// Draw separator + description:
	if (!m_strDescription.IsEmpty() && m_Params.m_bDrawDescription)
	{
		CRect rectDescr = rectText;
		rectDescr.top += nTextHeight + 3 * m_ptMargin.y / 2;

		if (m_Params.m_bDrawSeparator)
		{
			OnDrawSeparator(pDC, rectDescr.left, rectDescr.right, rectDescr.top - m_ptMargin.y / 2);
		}

		OnDrawDescription(pDC, rectDescr, FALSE);
	}

	pDC->SelectObject(pOldPen);
}

void CMFCToolTipCtrl::OnShow(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
	*pResult = 0;

	if (m_Params.m_bVislManagerTheme)
	{
		CMFCVisualManager::GetInstance()->GetToolTipInfo(m_Params);
		m_Params.m_bVislManagerTheme = TRUE;
	}

	if (m_Params.m_bBalloonTooltip)
	{
		return;
	}

	CPoint ptCursor;
	::GetCursorPos(&ptCursor);

	GetHotButton();

	m_sizeImage = m_Params.m_bDrawIcon ? GetIconSize() : CSize(0, 0);
	m_ptMargin = m_Params.m_bRoundedCorners ? CPoint(6, 4) : CPoint(4, 2);

	CRect rectMargin;
	GetMargin(rectMargin);

	CRect rectText;
	GetClientRect(rectText);

	CClientDC dc(this);
	CSize sizeText = OnDrawLabel(&dc, rectText, TRUE);

	int cx = sizeText.cx;
	int cy = sizeText.cy;

	CSize sizeDescr(0, 0);

	if (!m_Params.m_bDrawDescription || m_strDescription.IsEmpty())
	{
		cy = max(cy, m_sizeImage.cy);
	}
	else
	{
		sizeDescr = OnDrawDescription(&dc, rectText, TRUE);

		cy += sizeDescr.cy + 2 * m_ptMargin.y;
		cx = max(cx, sizeDescr.cx);
		cy = max(cy, m_sizeImage.cy);
	}

	if (m_sizeImage.cx > 0 && m_Params.m_bDrawIcon)
	{
		cx += m_sizeImage.cx + m_ptMargin.x;
	}

	cx += 2 * m_ptMargin.x;
	cy += 2 * m_ptMargin.y;

	const int nFixedWidth = GetFixedWidth();
	if (nFixedWidth > 0 && sizeDescr != CSize(0, 0))
	{
		cx = max(cx, nFixedWidth);
	}

	CRect rectWindow;
	GetWindowRect(rectWindow);

	int x = rectWindow.left;
	int y = rectWindow.top;

	if (m_ptLocation != CPoint(-1, -1))
	{
		x = m_ptLocation.x;
		y = m_ptLocation.y;

		*pResult = 1;
	}

	CRect rectScreen;

	MONITORINFO mi;
	mi.cbSize = sizeof(MONITORINFO);
	if (GetMonitorInfo(MonitorFromPoint(rectWindow.TopLeft(), MONITOR_DEFAULTTONEAREST), &mi))
	{
		rectScreen = mi.rcWork;
	}
	else
	{
		::SystemParametersInfo(SPI_GETWORKAREA, 0, &rectScreen, 0);
	}

	int nBottom = max(ptCursor.y + cy + ::GetSystemMetrics(SM_CYCURSOR), y + cy + 2);
	if (nBottom > rectScreen.bottom)
	{
		y = ptCursor.y - cy - 1;
		*pResult = 1;
	}

	if (x + cx + 2 > rectScreen.right)
	{
		if ((*pResult) == 1) // Y has been changed
		{
			x = ptCursor.x - cx - 1;
		}
		else
		{
			x = rectScreen.right - cx - 1;
			*pResult = 1;
		}
	}

	if ((*pResult) == 1)
	{
		SetWindowPos(NULL, x, y, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE);
	}
	else
	{
		SetWindowPos(NULL, -1, -1, cx, cy, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
	}

	if (m_Params.m_bRoundedCorners)
	{
		CRgn rgn;
		rgn.CreateRoundRectRgn(0, 0, cx + 1, cy + 1, 4, 4);

		SetWindowRgn(rgn, FALSE);
	}
}

void CMFCToolTipCtrl::OnPop(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
	m_pHotButton = NULL;
	m_pToolBarImages = NULL;
	m_strDescription.Empty();
	m_ptLocation = CPoint(-1, -1);
	m_pRibbonButton = NULL;
	m_nRibbonImageType = 0;
	*pResult = 0;
}

void CMFCToolTipCtrl::OnFillBackground(CDC* pDC, CRect rect, COLORREF& /*clrText*/, COLORREF& /*clrLine*/)
{
	ASSERT_VALID(pDC);

	if (m_Params.m_clrFill == (COLORREF)-1)
	{
		::FillRect(pDC->GetSafeHdc(), rect, ::GetSysColorBrush(COLOR_INFOBK));
	}
	else
	{
		if (m_Params.m_clrFillGradient == (COLORREF)-1)
		{
			CBrush br(m_Params.m_clrFill);
			pDC->FillRect(rect, &br);
		}
		else
		{
			CDrawingManager dm(*pDC);

			dm.FillGradient2(rect, m_Params.m_clrFillGradient, m_Params.m_clrFill, m_Params.m_nGradientAngle == -1 ? 90 : m_Params.m_nGradientAngle);
		}
	}
}

CSize CMFCToolTipCtrl::GetIconSize()
{

	if (m_pRibbonButton != NULL)
	{
		ASSERT_VALID(m_pRibbonButton);

		if (!m_pRibbonButton->IsDrawTooltipImage())
		{
			return CSize(0, 0);
		}

		if (m_pRibbonButton->GetIcon() != NULL)
		{
			m_nRibbonImageType = m_pRibbonButton->IsLargeImage() ? CMFCRibbonButton::RibbonImageLarge : CMFCRibbonButton::RibbonImageSmall;

			return m_pRibbonButton->GetImageSize((CMFCRibbonBaseElement::RibbonImageType)m_nRibbonImageType);
		}

		CSize sizeLarge(0, 0);

		if (m_pRibbonButton->IsLargeImage() && m_pRibbonButton->GetImageIndex(TRUE) >= 0)
		{
			sizeLarge = m_pRibbonButton->GetImageSize(CMFCRibbonButton::RibbonImageLarge);
		}

		if (sizeLarge != CSize(0, 0))
		{
			m_nRibbonImageType = CMFCRibbonButton::RibbonImageLarge;
			return sizeLarge;
		}

		CSize sizeSmall(0, 0);

		if (m_pRibbonButton->GetImageIndex(FALSE) >= 0)
		{
			sizeSmall = m_pRibbonButton->GetImageSize(CMFCRibbonButton::RibbonImageSmall);
		}

		m_nRibbonImageType = CMFCRibbonButton::RibbonImageSmall;

		return sizeSmall;
	}

	if (m_pHotButton == NULL || m_pToolBarImages == NULL || m_pToolBarImages->GetCount() == 0)
	{
		return CSize(0, 0);
	}

	ASSERT_VALID(m_pHotButton);

	return m_pHotButton->GetImage() >= 0 ? m_pToolBarImages->GetImageSize() : CSize(0, 0);
}

void CMFCToolTipCtrl::OnDrawBorder(CDC* pDC, CRect rect, COLORREF clrLine)
{
	ASSERT_VALID(pDC);

	if (!m_Params.m_bRoundedCorners)
	{
		pDC->Draw3dRect(rect, clrLine, clrLine);
		return;
	}

	const int nOffset = 2;

	pDC->MoveTo(rect.left + nOffset, rect.top);
	pDC->LineTo(rect.right - nOffset - 1, rect.top);

	pDC->LineTo(rect.right - 1, rect.top + nOffset);
	pDC->LineTo(rect.right - 1, rect.bottom - 1 - nOffset);

	pDC->LineTo(rect.right - nOffset - 1, rect.bottom - 1);
	pDC->LineTo(rect.left + nOffset, rect.bottom - 1);

	pDC->LineTo(rect.left, rect.bottom - 1 - nOffset);
	pDC->LineTo(rect.left, rect.top + nOffset);

	pDC->LineTo(rect.left + nOffset, rect.top);
}

BOOL CMFCToolTipCtrl::OnDrawIcon(CDC* pDC, CRect rectImage)
{
	ASSERT_VALID(pDC);

	if (m_pRibbonButton != NULL)
	{
		ASSERT_VALID(m_pRibbonButton);

		BOOL bIsDisabled = m_pRibbonButton->m_bIsDisabled;
		m_pRibbonButton->m_bIsDisabled = FALSE;

		m_pRibbonButton->DrawImage(pDC, (CMFCRibbonBaseElement::RibbonImageType) m_nRibbonImageType, rectImage);

		m_pRibbonButton->m_bIsDisabled = bIsDisabled;
		return TRUE;
	}

	if (m_pHotButton == NULL || m_pToolBarImages == NULL)
	{
		return FALSE;
	}

	ASSERT_VALID(m_pHotButton);
	ASSERT_VALID(m_pToolBarImages);

	CAfxDrawState ds;
	m_pToolBarImages->PrepareDrawImage(ds);

	UINT nSaveStyle = m_pHotButton->m_nStyle;
	BOOL bSaveText = m_pHotButton->m_bText;
	BOOL bSaveImage = m_pHotButton->m_bImage;

	BOOL bSaveLargeIcons = CMFCToolBar::m_bLargeIcons;
	CMFCToolBar::m_bLargeIcons = FALSE;

	m_pHotButton->m_bText = FALSE;
	m_pHotButton->m_bImage = TRUE;

	m_pHotButton->m_nStyle = 0;

	m_pHotButton->CMFCToolBarButton::OnDraw(pDC, rectImage, m_pToolBarImages);

	m_pHotButton->m_nStyle = nSaveStyle;
	m_pHotButton->m_bText = bSaveText;
	m_pHotButton->m_bImage = bSaveImage;

	CMFCToolBar::m_bLargeIcons = bSaveLargeIcons;

	m_pToolBarImages->EndDrawImage(ds);
	return TRUE;
}

CSize CMFCToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
{
	ASSERT_VALID(pDC);

	CSize sizeText(0, 0);

	CString strText;
	GetWindowText(strText);

	strText.Replace(_T("\t"), _T("    "));

	BOOL bDrawDescr = m_Params.m_bDrawDescription && !m_strDescription.IsEmpty();

	CFont* pOldFont = (CFont*) pDC->SelectObject(m_Params.m_bBoldLabel && bDrawDescr ? &afxGlobalData.fontBold : &afxGlobalData.fontTooltip);

	if (strText.Find(_T('\n')) >= 0) // Multi-line text
	{
		UINT nFormat = DT_NOPREFIX;
		if (bCalcOnly)
		{
			nFormat |= DT_CALCRECT;
		}

		if (m_pRibbonButton != NULL)
		{
			nFormat |= DT_NOPREFIX;
		}

		int nHeight = pDC->DrawText(strText, rect, nFormat);
		sizeText = CSize(rect.Width(), nHeight);
	}
	else
	{
		if (bCalcOnly)
		{
			sizeText = pDC->GetTextExtent(strText);
		}
		else
		{
			UINT nFormat = DT_LEFT | DT_NOCLIP | DT_SINGLELINE;

			if (!bDrawDescr)
			{
				nFormat |= DT_VCENTER;
			}

			if (m_pRibbonButton != NULL)
			{
				nFormat |= DT_NOPREFIX;
			}

			sizeText.cy = pDC->DrawText(strText, rect, nFormat);
			sizeText.cx = rect.Width();
		}
	}

	pDC->SelectObject(pOldFont);

	return sizeText;
}

CSize CMFCToolTipCtrl::OnDrawDescription(CDC* pDC, CRect rect, BOOL bCalcOnly)
{
	ASSERT_VALID(pDC);

	CSize sizeText(0, 0);

	if (!m_Params.m_bDrawDescription)
	{
		return sizeText;
	}

	CFont* pOldFont = pDC->SelectObject(&afxGlobalData.fontTooltip);
	int nFixedWidth = GetFixedWidth ();

	if (nFixedWidth > 0 && m_sizeImage.cx <= 32)
	{
		rect.right = rect.left + nFixedWidth;

		if (m_sizeImage.cx > 0 && m_Params.m_bDrawIcon)
		{
			rect.right -= m_sizeImage.cx + m_ptMargin.x;
		}
	}
	else
	{
		rect.right = rect.left + m_Params.m_nMaxDescrWidth;
	}

	UINT nFormat = DT_WORDBREAK;
	if (bCalcOnly)
	{
		nFormat |= DT_CALCRECT;
	}

	int nDescrHeight = pDC->DrawText(m_strDescription, rect, nFormat);
	pDC->SelectObject(pOldFont);

	return CSize(rect.Width(), nDescrHeight);
}

void CMFCToolTipCtrl::OnDrawSeparator(CDC* pDC, int x1, int x2, int y)
{
	ASSERT_VALID(pDC);

	pDC->MoveTo(x1, y);
	pDC->LineTo(x2, y);
}

int CMFCToolTipCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CToolTipCtrl::OnCreate(lpCreateStruct) == -1)
		return -1;

	m_pToolBar = DYNAMIC_DOWNCAST(CMFCToolBar, CWnd::FromHandlePermanent(lpCreateStruct->hwndParent));

	if (m_pToolBar != NULL && m_pToolBar->IsKindOf(RUNTIME_CLASS(CMFCOutlookBarPane)))
	{
		m_pToolBar = NULL;
	}

	ModifyStyle(WS_BORDER, 0);

	if (m_Params.m_bBalloonTooltip)
	{
		ModifyStyle(0, TTS_BALLOON);
	}

	return 0;
}

{
	CRect rect;
    GetClientRect (rect);

    COLORREF clrDummy;
    OnFillBackground (pDC, rect, clrDummy, clrDummy);

	return TRUE;
}

void CMFCToolTipCtrl::SetParams(CMFCToolTipInfo* pParams)
{
	ASSERT_VALID(this);

	if (pParams == NULL)
	{
		CMFCToolTipInfo paramsDefault;
		m_Params = paramsDefault;
	}
	else
	{
		m_Params = *pParams;
	}
}

void CMFCToolTipCtrl::SetDescription(const CString strDescription)
{
	ASSERT_VALID(this);

	GetHotButton();
	m_strDescription = strDescription;

	m_strDescription.Replace(_T("\t"), _T("    "));
}

void CMFCToolTipCtrl::GetHotButton()
{
	m_pHotButton = NULL;
	m_pToolBarImages = NULL;

	if (m_pRibbonButton != NULL)
	{
		return;
	}

	if (m_pToolBar != NULL)
	{
		CPoint ptToolBar;

		::GetCursorPos(&ptToolBar);
		m_pToolBar->ScreenToClient(&ptToolBar);

		m_pHotButton = m_pToolBar->GetButton(m_pToolBar->HitTest(ptToolBar));

		if (m_pHotButton != NULL)
		{
			if (m_pToolBar->IsLocked())
			{
				m_pToolBarImages = m_pToolBar->GetLockedMenuImages();
			}
			else
			{
				if (m_pHotButton->m_bUserButton)
				{
					m_pToolBarImages = CMFCToolBar::GetUserImages();
				}
				else
				{
					m_pToolBarImages = CMFCToolBar::GetMenuImages();
					if (m_pToolBarImages == NULL || m_pToolBarImages->GetCount() <= 0)
					{
						m_pToolBarImages = CMFCToolBar::GetImages();
					}
				}
			}
		}
	}
}

void CMFCToolTipCtrl::SetLocation(CPoint pt)
{
	ASSERT_VALID(this);
	m_ptLocation = pt;
}

void CMFCToolTipCtrl::SetHotRibbonButton(CMFCRibbonButton* pRibbonButton)
{
	ASSERT_VALID(this);
	m_pRibbonButton = pRibbonButton;
}

int CMFCToolTipCtrl::GetFixedWidth()
{
	ASSERT_VALID(this);

	if (m_sizeImage.cx <= (int)(afxGlobalData.GetRibbonImageScale() * 32))
	{
		return m_nFixedWidthRegular;
	}
	else
	{
		return m_nFixedWidthWithImage;
	}
}


// This MFC Library source code supports the Microsoft Office Fluent User Interface
// (the "Fluent UI") and is provided only as referential material to supplement the
// Microsoft Foundation Classes Reference and related electronic documentation
// included with the MFC C++ library software.
// License terms to copy, use or distribute the Fluent UI are available separately.
// To learn more about our Fluent UI licensing program, please visit
// All rights reserved.

#include "stdafx.h"
#include "multimon.h"
#include "afxtooltipctrl.h"

#include "afxtoolbarimages.h"
#include "afxtoolbar.h"
#include "afxtoolbarbutton.h"
#include "afxdrawmanager.h"
#include "afxvisualmanager.h"
#include "afxoutlookbarpane.h"
#include "afxribbonbutton.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#ifndef TTS_BALLOON
#define TTS_BALLOON 0x40
#endif

#ifndef TTM_SETTITLE
#define TTM_SETTITLE (WM_USER + 32)
#endif

// CMFCToolTipCtrl

IMPLEMENT_DYNCREATE(CMFCToolTipCtrl, CToolTipCtrl)

CMFCToolTipCtrl::CMFCToolTipCtrl(CMFCToolTipInfo* pParams/* = NULL*/)
{
	SetParams(pParams);

	m_pToolBar = NULL;
	m_pToolBarImages = NULL;
	m_pHotButton = NULL;
	m_sizeImage = CSize(0, 0);
	m_ptMargin = CPoint(0, 0);
	m_ptLocation = CPoint(-1, -1);
	m_pRibbonButton = NULL;
	m_nRibbonImageType = 0;
	m_nFixedWidthRegular = 0;
	m_nFixedWidthWithImage = 0;
}

CMFCToolTipCtrl::~CMFCToolTipCtrl()

BEGIN_MESSAGE_MAP(CMFCToolTipCtrl, CToolTipCtrl)
	ON_WM_PAINT()
	ON_WM_CREATE()
	ON_WM_ERASEBKGND()
	ON_NOTIFY_REFLECT(TTN_SHOW, &CMFCToolTipCtrl::OnShow)
	ON_NOTIFY_REFLECT(TTN_POP, &CMFCToolTipCtrl::OnPop)
END_MESSAGE_MAP()

// CMFCToolTipCtrl message handlers

void CMFCToolTipCtrl::OnPaint()
{
	if (m_Params.m_bBalloonTooltip)
	{
		CToolTipCtrl::OnPaint();
		return;
	}

	CPaintDC dcPaint(this); // device context for painting

	CMemDC memDC (dcPaint, this);
	CDC* pDC = &memDC.GetDC ();

	CRect rect;
	GetClientRect(rect);

	CRect rectMargin;
	GetMargin(rectMargin);

	CRect rectText = rect;

	rectText.DeflateRect(rectMargin);
	rectText.DeflateRect(m_ptMargin.x, m_ptMargin.y);

	COLORREF clrLine = m_Params.m_clrBorder == (COLORREF)-1 ? ::GetSysColor(COLOR_INFOTEXT) : m_Params.m_clrBorder;
	COLORREF clrText = m_Params.m_clrText == (COLORREF)-1 ? ::GetSysColor(COLOR_INFOTEXT) : m_Params.m_clrText;

	// Fill background:
	OnFillBackground(pDC, rect, clrText, clrLine);

	CPen penLine(PS_SOLID, 1, clrLine);
	CPen* pOldPen = pDC->SelectObject(&penLine);

	// Draw border:
	OnDrawBorder(pDC, rect, clrLine);

	// Draw icon:
	if (m_sizeImage != CSize(0, 0) && m_Params.m_bDrawIcon)
	{
		CRect rectImage = rectText;
		rectImage.right = rectImage.left + m_sizeImage.cx;
		rectImage.bottom = rectImage.top + m_sizeImage.cy;

		OnDrawIcon(pDC, rectImage);

		rectText.left += m_sizeImage.cx + m_ptMargin.x;
	}

	pDC->SetBkMode(TRANSPARENT);
	pDC->SetTextColor(clrText);

	// Draw label:
	int nTextHeight = OnDrawLabel(pDC, rectText, FALSE).cy;

	// Draw separator + description:
	if (!m_strDescription.IsEmpty() && m_Params.m_bDrawDescription)
	{
		CRect rectDescr = rectText;
		rectDescr.top += nTextHeight + 3 * m_ptMargin.y / 2;

		if (m_Params.m_bDrawSeparator)
		{
			OnDrawSeparator(pDC, rectDescr.left, rectDescr.right, rectDescr.top - m_ptMargin.y / 2);
		}

		OnDrawDescription(pDC, rectDescr, FALSE);
	}

	pDC->SelectObject(pOldPen);
}

void CMFCToolTipCtrl::OnShow(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
	*pResult = 0;

	if (m_Params.m_bVislManagerTheme)
	{
		CMFCVisualManager::GetInstance()->GetToolTipInfo(m_Params);
		m_Params.m_bVislManagerTheme = TRUE;
	}

	if (m_Params.m_bBalloonTooltip)
	{
		return;
	}

	CPoint ptCursor;
	::GetCursorPos(&ptCursor);

	GetHotButton();

	m_sizeImage = m_Params.m_bDrawIcon ? GetIconSize() : CSize(0, 0);
	m_ptMargin = m_Params.m_bRoundedCorners ? CPoint(6, 4) : CPoint(4, 2);

	CRect rectMargin;
	GetMargin(rectMargin);

	CRect rectText;
	GetClientRect(rectText);

	CClientDC dc(this);
	CSize sizeText = OnDrawLabel(&dc, rectText, TRUE);

	int cx = sizeText.cx;
	int cy = sizeText.cy;

	CSize sizeDescr(0, 0);

	if (!m_Params.m_bDrawDescription || m_strDescription.IsEmpty())
	{
		cy = max(cy, m_sizeImage.cy);
	}
	else
	{
		sizeDescr = OnDrawDescription(&dc, rectText, TRUE);

		cy += sizeDescr.cy + 2 * m_ptMargin.y;
		cx = max(cx, sizeDescr.cx);
		cy = max(cy, m_sizeImage.cy);
	}

	if (m_sizeImage.cx > 0 && m_Params.m_bDrawIcon)
	{
		cx += m_sizeImage.cx + m_ptMargin.x;
	}

	cx += 2 * m_ptMargin.x;
	cy += 2 * m_ptMargin.y;

	const int nFixedWidth = GetFixedWidth();
	if (nFixedWidth > 0 && sizeDescr != CSize(0, 0))
	{
		cx = max(cx, nFixedWidth);
	}

	CRect rectWindow;
	GetWindowRect(rectWindow);

	int x = rectWindow.left;
	int y = rectWindow.top;

	if (m_ptLocation != CPoint(-1, -1))
	{
		x = m_ptLocation.x;
		y = m_ptLocation.y;

		*pResult = 1;
	}

	CRect rectScreen;

	MONITORINFO mi;
	mi.cbSize = sizeof(MONITORINFO);
	if (GetMonitorInfo(MonitorFromPoint(rectWindow.TopLeft(), MONITOR_DEFAULTTONEAREST), &mi))
	{
		rectScreen = mi.rcWork;
	}
	else
	{
		::SystemParametersInfo(SPI_GETWORKAREA, 0, &rectScreen, 0);
	}

	int nBottom = max(ptCursor.y + cy + ::GetSystemMetrics(SM_CYCURSOR), y + cy + 2);
	if (nBottom > rectScreen.bottom)
	{
		y = ptCursor.y - cy - 1;
		*pResult = 1;
	}

	if (x + cx + 2 > rectScreen.right)
	{
		if ((*pResult) == 1) // Y has been changed
		{
			x = ptCursor.x - cx - 1;
		}
		else
		{
			x = rectScreen.right - cx - 1;
			*pResult = 1;
		}
	}

	if ((*pResult) == 1)
	{
		SetWindowPos(NULL, x, y, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE);
	}
	else
	{
		SetWindowPos(NULL, -1, -1, cx, cy, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
	}

	if (m_Params.m_bRoundedCorners)
	{
		CRgn rgn;
		rgn.CreateRoundRectRgn(0, 0, cx + 1, cy + 1, 4, 4);

		SetWindowRgn(rgn, FALSE);
	}
}

void CMFCToolTipCtrl::OnPop(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
	m_pHotButton = NULL;
	m_pToolBarImages = NULL;
	m_strDescription.Empty();
	m_ptLocation = CPoint(-1, -1);
	m_pRibbonButton = NULL;
	m_nRibbonImageType = 0;
	*pResult = 0;
}

void CMFCToolTipCtrl::OnFillBackground(CDC* pDC, CRect rect, COLORREF& /*clrText*/, COLORREF& /*clrLine*/)
{
	ASSERT_VALID(pDC);

	if (m_Params.m_clrFill == (COLORREF)-1)
	{
		::FillRect(pDC->GetSafeHdc(), rect, ::GetSysColorBrush(COLOR_INFOBK));
	}
	else
	{
		if (m_Params.m_clrFillGradient == (COLORREF)-1)
		{
			CBrush br(m_Params.m_clrFill);
			pDC->FillRect(rect, &br);
		}
		else
		{
			CDrawingManager dm(*pDC);

			dm.FillGradient2(rect, m_Params.m_clrFillGradient, m_Params.m_clrFill, m_Params.m_nGradientAngle == -1 ? 90 : m_Params.m_nGradientAngle);
		}
	}
}

CSize CMFCToolTipCtrl::GetIconSize()
{

	if (m_pRibbonButton != NULL)
	{
		ASSERT_VALID(m_pRibbonButton);

		if (!m_pRibbonButton->IsDrawTooltipImage())
		{
			return CSize(0, 0);
		}

		if (m_pRibbonButton->GetIcon() != NULL)
		{
			m_nRibbonImageType = m_pRibbonButton->IsLargeImage() ? CMFCRibbonButton::RibbonImageLarge : CMFCRibbonButton::RibbonImageSmall;

			return m_pRibbonButton->GetImageSize((CMFCRibbonBaseElement::RibbonImageType)m_nRibbonImageType);
		}

		CSize sizeLarge(0, 0);

		if (m_pRibbonButton->IsLargeImage() && m_pRibbonButton->GetImageIndex(TRUE) >= 0)
		{
			sizeLarge = m_pRibbonButton->GetImageSize(CMFCRibbonButton::RibbonImageLarge);
		}

		if (sizeLarge != CSize(0, 0))
		{
			m_nRibbonImageType = CMFCRibbonButton::RibbonImageLarge;
			return sizeLarge;
		}

		CSize sizeSmall(0, 0);

		if (m_pRibbonButton->GetImageIndex(FALSE) >= 0)
		{
			sizeSmall = m_pRibbonButton->GetImageSize(CMFCRibbonButton::RibbonImageSmall);
		}

		m_nRibbonImageType = CMFCRibbonButton::RibbonImageSmall;

		return sizeSmall;
	}

	if (m_pHotButton == NULL || m_pToolBarImages == NULL || m_pToolBarImages->GetCount() == 0)
	{
		return CSize(0, 0);
	}

	ASSERT_VALID(m_pHotButton);

	return m_pHotButton->GetImage() >= 0 ? m_pToolBarImages->GetImageSize() : CSize(0, 0);
}

void CMFCToolTipCtrl::OnDrawBorder(CDC* pDC, CRect rect, COLORREF clrLine)
{
	ASSERT_VALID(pDC);

	if (!m_Params.m_bRoundedCorners)
	{
		pDC->Draw3dRect(rect, clrLine, clrLine);
		return;
	}

	const int nOffset = 2;

	pDC->MoveTo(rect.left + nOffset, rect.top);
	pDC->LineTo(rect.right - nOffset - 1, rect.top);

	pDC->LineTo(rect.right - 1, rect.top + nOffset);
	pDC->LineTo(rect.right - 1, rect.bottom - 1 - nOffset);

	pDC->LineTo(rect.right - nOffset - 1, rect.bottom - 1);
	pDC->LineTo(rect.left + nOffset, rect.bottom - 1);

	pDC->LineTo(rect.left, rect.bottom - 1 - nOffset);
	pDC->LineTo(rect.left, rect.top + nOffset);

	pDC->LineTo(rect.left + nOffset, rect.top);
}

BOOL CMFCToolTipCtrl::OnDrawIcon(CDC* pDC, CRect rectImage)
{
	ASSERT_VALID(pDC);

	if (m_pRibbonButton != NULL)
	{
		ASSERT_VALID(m_pRibbonButton);

		BOOL bIsDisabled = m_pRibbonButton->m_bIsDisabled;
		m_pRibbonButton->m_bIsDisabled = FALSE;

		m_pRibbonButton->DrawImage(pDC, (CMFCRibbonBaseElement::RibbonImageType) m_nRibbonImageType, rectImage);

		m_pRibbonButton->m_bIsDisabled = bIsDisabled;
		return TRUE;
	}

	if (m_pHotButton == NULL || m_pToolBarImages == NULL)
	{
		return FALSE;
	}

	ASSERT_VALID(m_pHotButton);
	ASSERT_VALID(m_pToolBarImages);

	CAfxDrawState ds;
	m_pToolBarImages->PrepareDrawImage(ds);

	UINT nSaveStyle = m_pHotButton->m_nStyle;
	BOOL bSaveText = m_pHotButton->m_bText;
	BOOL bSaveImage = m_pHotButton->m_bImage;

	BOOL bSaveLargeIcons = CMFCToolBar::m_bLargeIcons;
	CMFCToolBar::m_bLargeIcons = FALSE;

	m_pHotButton->m_bText = FALSE;
	m_pHotButton->m_bImage = TRUE;

	m_pHotButton->m_nStyle = 0;

	m_pHotButton->CMFCToolBarButton::OnDraw(pDC, rectImage, m_pToolBarImages);

	m_pHotButton->m_nStyle = nSaveStyle;
	m_pHotButton->m_bText = bSaveText;
	m_pHotButton->m_bImage = bSaveImage;

	CMFCToolBar::m_bLargeIcons = bSaveLargeIcons;

	m_pToolBarImages->EndDrawImage(ds);
	return TRUE;
}

CSize CMFCToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
{
	ASSERT_VALID(pDC);

	CSize sizeText(0, 0);

	CString strText;
	GetWindowText(strText);

	strText.Replace(_T("\t"), _T("    "));

	BOOL bDrawDescr = m_Params.m_bDrawDescription && !m_strDescription.IsEmpty();

	CFont* pOldFont = (CFont*) pDC->SelectObject(m_Params.m_bBoldLabel && bDrawDescr ? &afxGlobalData.fontBold : &afxGlobalData.fontTooltip);

	if (strText.Find(_T('\n')) >= 0) // Multi-line text
	{
		UINT nFormat = DT_NOPREFIX;
		if (bCalcOnly)
		{
			nFormat |= DT_CALCRECT;
		}

		if (m_pRibbonButton != NULL)
		{
			nFormat |= DT_NOPREFIX;
		}

		int nHeight = pDC->DrawText(strText, rect, nFormat);
		sizeText = CSize(rect.Width(), nHeight);
	}
	else
	{
		if (bCalcOnly)
		{
			sizeText = pDC->GetTextExtent(strText);
		}
		else
		{
			UINT nFormat = DT_LEFT | DT_NOCLIP | DT_SINGLELINE;

			if (!bDrawDescr)
			{
				nFormat |= DT_VCENTER;
			}

			if (m_pRibbonButton != NULL)
			{
				nFormat |= DT_NOPREFIX;
			}

			sizeText.cy = pDC->DrawText(strText, rect, nFormat);
			sizeText.cx = rect.Width();
		}
	}

	pDC->SelectObject(pOldFont);

	return sizeText;
}

CSize CMFCToolTipCtrl::OnDrawDescription(CDC* pDC, CRect rect, BOOL bCalcOnly)
{
	ASSERT_VALID(pDC);

	CSize sizeText(0, 0);

	if (!m_Params.m_bDrawDescription)
	{
		return sizeText;
	}

	CFont* pOldFont = pDC->SelectObject(&afxGlobalData.fontTooltip);
	int nFixedWidth = GetFixedWidth ();

	if (nFixedWidth > 0 && m_sizeImage.cx <= 32)
	{
		rect.right = rect.left + nFixedWidth;

		if (m_sizeImage.cx > 0 && m_Params.m_bDrawIcon)
		{
			rect.right -= m_sizeImage.cx + m_ptMargin.x;
		}
	}
	else
	{
		rect.right = rect.left + m_Params.m_nMaxDescrWidth;
	}

	UINT nFormat = DT_WORDBREAK;
	if (bCalcOnly)
	{
		nFormat |= DT_CALCRECT;
	}

	int nDescrHeight = pDC->DrawText(m_strDescription, rect, nFormat);
	pDC->SelectObject(pOldFont);

	return CSize(rect.Width(), nDescrHeight);
}

void CMFCToolTipCtrl::OnDrawSeparator(CDC* pDC, int x1, int x2, int y)
{
	ASSERT_VALID(pDC);

	pDC->MoveTo(x1, y);
	pDC->LineTo(x2, y);
}

int CMFCToolTipCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CToolTipCtrl::OnCreate(lpCreateStruct) == -1)
		return -1;

	m_pToolBar = DYNAMIC_DOWNCAST(CMFCToolBar, CWnd::FromHandlePermanent(lpCreateStruct->hwndParent));

	if (m_pToolBar != NULL && m_pToolBar->IsKindOf(RUNTIME_CLASS(CMFCOutlookBarPane)))
	{
		m_pToolBar = NULL;
	}

	ModifyStyle(WS_BORDER, 0);

	if (m_Params.m_bBalloonTooltip)
	{
		ModifyStyle(0, TTS_BALLOON);
	}

	return 0;
}

BOOL CMFCToolTipCtrl::OnEraseBkgnd(CDC* pDC)
{
	CRect rect;
    GetClientRect (rect);

    COLORREF clrDummy;
    OnFillBackground (pDC, rect, clrDummy, clrDummy);

	return TRUE;
}

void CMFCToolTipCtrl::SetParams(CMFCToolTipInfo* pParams)
{
	ASSERT_VALID(this);

	if (pParams == NULL)
	{
		CMFCToolTipInfo paramsDefault;
		m_Params = paramsDefault;
	}
	else
	{
		m_Params = *pParams;
	}
}

void CMFCToolTipCtrl::SetDescription(const CString strDescription)
{
	ASSERT_VALID(this);

	GetHotButton();
	m_strDescription = strDescription;

	m_strDescription.Replace(_T("\t"), _T("    "));
}

void CMFCToolTipCtrl::GetHotButton()
{
	m_pHotButton = NULL;
	m_pToolBarImages = NULL;

	if (m_pRibbonButton != NULL)
	{
		return;
	}

	if (m_pToolBar != NULL)
	{
		CPoint ptToolBar;

		::GetCursorPos(&ptToolBar);
		m_pToolBar->ScreenToClient(&ptToolBar);

		m_pHotButton = m_pToolBar->GetButton(m_pToolBar->HitTest(ptToolBar));

		if (m_pHotButton != NULL)
		{
			if (m_pToolBar->IsLocked())
			{
				m_pToolBarImages = m_pToolBar->GetLockedMenuImages();
			}
			else
			{
				if (m_pHotButton->m_bUserButton)
				{
					m_pToolBarImages = CMFCToolBar::GetUserImages();
				}
				else
				{
					m_pToolBarImages = CMFCToolBar::GetMenuImages();
					if (m_pToolBarImages == NULL || m_pToolBarImages->GetCount() <= 0)
					{
						m_pToolBarImages = CMFCToolBar::GetImages();
					}
				}
			}
		}
	}
}

void CMFCToolTipCtrl::SetLocation(CPoint pt)
{
	ASSERT_VALID(this);
	m_ptLocation = pt;
}

void CMFCToolTipCtrl::SetHotRibbonButton(CMFCRibbonButton* pRibbonButton)
{
	ASSERT_VALID(this);
	m_pRibbonButton = pRibbonButton;
}

int CMFCToolTipCtrl::GetFixedWidth()
{
	ASSERT_VALID(this);

	if (m_sizeImage.cx <= (int)(afxGlobalData.GetRibbonImageScale() * 32))
	{
		return m_nFixedWidthRegular;
	}
	else
	{
		return m_nFixedWidthWithImage;
	}
}