-
Notifications
You must be signed in to change notification settings - Fork 511
Mouse
DirectXTK | DirectXTK12 |
---|
This is a helper for simplified mouse tracking modeled after the XNA Game Studio 4 (Microsoft.Xna.Framework.Input
) Mouse class.
Related tutorial: Mouse and keyboard input
#include <Mouse.h>
Mouse is a singleton.
std::unique_ptr<Mouse> mouse;
mouse = std::make_unique<Mouse>();
For exception safety, it is recommended you make use of the C++ RAII pattern and use a std::unique_ptr
.
The application needs to call SetWindow and make calls during the main WndProc
message processing to ProcessMessage:
#include <Windows.h>
#include "Mouse.h"
mouse->SetWindow(hWnd);
...
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ACTIVATE:
case WM_ACTIVATEAPP:
case WM_INPUT:
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_MOUSEWHEEL:
case WM_XBUTTONDOWN:
case WM_XBUTTONUP:
case WM_MOUSEHOVER:
Mouse::ProcessMessage(message, wParam, lParam);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
One additional message is optional depending on desired application behavior. When your application has lost focus, and the user "click activates" it with the mouse, you will see that first mouse button event come through the Mouse interface. If you want the system to automatically ignore that first mouse button click when reactivating the window this way, add the following to your WndProc
:
case WM_MOUSEACTIVATE:
// When you click to activate the window, we want Mouse to ignore that event.
return MA_ACTIVATEANDEAT;
You need to call SetWindow and SetDpi in the appropriate places.
void ViewProvider::SetWindow(CoreWindow^ window)
{
mouse->SetWindow(window);
auto currentDisplayInformation = DisplayInformation::GetForCurrentView();
currentDisplayInformation->DpiChanged +=
ref new TypedEventHandler<DisplayInformation^, Object^>(this, &ViewProvider::OnDpiChanged);
mouse->SetDpi(currentDisplayInformation->LogicalDpi);
}
void ViewProvider::OnDpiChanged(DisplayInformation^ sender, Object^ args)
{
Mouse::SetDpi(sender->LogicalDpi);
}
void ViewProvider::SetWindow(winrt::Windows::UI::Core::CoreWindow window window)
{
mouse->SetWindow(window);
auto currentDisplayInformation = DisplayInformation::GetForCurrentView();
currentDisplayInformation.DpiChanged({ this, &ViewProvider::OnDpiChanged });
mouse->SetDpi(currentDisplayInformation.LogicalDpi());
}
void ViewProvider::OnDpiChanged(DisplayInformation const & sender, IInspectable const & /*args*/)
{
Mouse::SetDpi(sender.LogicalDpi());
}
You only get the C++/WinRT
SetWindow
helper function if you have includedwinrt/Windows.UI.Core.h
before you includeMouse.h
. Alternatively, you can always just do what the helper does for you:
mouse->SetWindow(reinterpret_cast<ABI::Windows::UI::Core::ICoreWindow*>(winrt::get_abi(window)));
For Microsoft GDKX when targeting Gaming.Xbox.*.x64, you follow the pattern above for Windows desktop, although only a subset of Win32 messages are required (i.e. it does not use WM_INPUT
, WM_MOUSEHOVER
, or WM_MOUSEACTIVATE
):
#include <Windows.h>
#include "Mouse.h"
mouse->SetWindow(hWnd);
...
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ACTIVATE:
case WM_ACTIVATEAPP:
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_MOUSEWHEEL:
case WM_XBUTTONDOWN:
case WM_XBUTTONUP:
Mouse::ProcessMessage(message, wParam, lParam);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
If using 4k or 1440p instead of 1080p, be sure to call SetResolution as well:
float uiScale = 1.f;
if (height == 2160) uiScale = 2.f;
else if (height == 1440) uiScale = 1.333333f;
Mouse::SetResolution(uiScale);
For the Xbox One XDK, you follow the pattern above for Universal Windows Platform (UWP) apps. You need to use SetDpi for 4k instead of 1080p:
Mouse::SetDpi((width == 3840) ? 192.f : 96.f);
GetState queries the current state of the mouse.
auto state = mouse->GetState();
if ( state.leftButton )
// Left button is down
XMFLOAT2 mousePosInPixels( float(m.x), float(m.y) );
// This is the absolute position of the mouse relative
// to the upper-left corner of the window
Since Mouse is a singleton, you can make use of the static method Get if desired:
auto state = Mouse::Get().GetState()
Mouse::State field | Description |
---|---|
bool leftButton | Left mouse button depressed |
bool middleButton | Middle mouse button depressed |
bool rightButton | Right mouse button depressed |
bool xButton1 | X1 mouse button (if present) depressed |
bool xButton2 | X2 mouse button (if present) depressed |
int x int y |
For absolute mode, pixel location in x/y. For relative mode, delta x/y. |
int scrollWheelValue | Scroll-wheel value. |
Mode positionMode | Indicates MODE_ABSOLUTE or MODE_RELATIVE . |
A common pattern is to trigger an action when a mouse button is pressed or released, but you don't want to trigger the action every single frame if the button is held down for more than a single frame. This helper class simplifies this.
Mouse::ButtonStateTracker tracker;
...
auto state = mouse->GetState();
tracker.Update( state );
if ( tracker.rightButton == Mouse::ButtonStateTracker::PRESSED )
// Take an action when Right mouse button is first pressed,
// but don't do it again until the button is released and
// then pressed again
Each button is reported by the tracker with a state:
-
PRESSED
: Indicates that the button was just pushed down since the previousUpdate
. -
RELEASED
: Indicates that the button was just let up since the previousUpdate
. -
UP
: This indicates the button has been up both for thisUpdate
and the previous one. -
HELD
: This indicates the button has been held down both for thisUpdate
and the previous one.
The
UP
andHELD
states are for convenience and readability as they provide exactly the same information as the result fromMouse::GetState
.
You may find that using Mouse::ButtonStateTracker::PRESSED
is a bit verbose. You can simplify the code by doing:
using ButtonState = Mouse::ButtonStateTracker::ButtonState;
if ( tracker.rightButton == ButtonState::PRESSED )
// Take an action when Right mouse button is first pressed,
// but don't do it again until the button is released and
// then pressed again
When resuming from a pause or suspend, be sure to call Reset on the tracker object to clear the state history.
By default, the mouse state is returned as an absolute pixel location in the x
and y
values of State
. For 'mouse-look' behavior in games, however, relative mouse movement is desired. While there are some older tricks for emulating this with absolute pixel locations and computing deltas, there are better options which are implemented by Mouse.
Control of the mode is set by SetMode passing either MODE_ABSOLUTE
(the default) or MODE_RELATIVE
. The current mode is returned in State
in the positionMode
value to inform your input code locally the mode of the x
, y
values.
Note: While in the relative mode, you should call
GetState
only once per frame as this resets the deltax
andy
values on return.
Here, we are using relative movement whenever the left mouse button is held down:
auto state = g_mouse->GetState();
if (state.positionMode == Mouse::MODE_RELATIVE)
{
// state.x and state.y are relative values; system cursor is not visible
}
else
{
// state.x and state.y are absolute pixel values; system cursor is visible
}
tracker.Update(state);
if (tracker.leftButton == Mouse::ButtonStateTracker::ButtonState::PRESSED)
{
mouse->SetMode(Mouse::MODE_RELATIVE);
}
else if (tracker.leftButton == Mouse::ButtonStateTracker::ButtonState::RELEASED)
{
mouse->SetMode(Mouse::MODE_ABSOLUTE);
}
When using
MODE_RELATIVE
, the system cursor is hidden so a user can't navigate away to another monitor or app or even exit using the mouse--they can of course use keyboard shortcuts (Alt+TAB or Alt+F4). If your game makes use of 'mouse-look' controls, you should ensure that a simple key (like the Esc key) returns to the game's menu/pause screen and that needs to restoreMODE_ABSOLUTE
behavior.
The system cursor is shown by default on Windows desktop systems. Mouse will hide the cursor when using relative mode, but restores it when returning to absolute mode.
The property IsVisible can be checked at any time. Use SetVisible to hide/show the cursor while in absolute mode.
The mouse scroll wheel value is accumulated. To reset the value to 0, use ResetScrollWheelValue.
The IsConnected method can be used to test if a mouse device is present on the system.
The Mouse class should be thread-safe with the exception of the ProcessMessage which should only be called in your windows message loop.
For Universal Windows Platform apps, touch/pointer devices are captured as mouse movement. Touch/pointer devices do not, however, result in changes to button state. Relative mouse movement is captured per this Microsoft Docs article.
For UWP applications on Xbox One, the game controller can be made to emulate a mouse which will provide input through the Mouse class, but the input paradigm is more natural if you use the GamePad class directly.
For Windows desktop apps, relative mouse movement is captured using "raw input" per the article Taking Advantage of High-Definition Mouse Movement. Note that a consequence of this implementation is that relative mouse movement is not available when using the application through Remote Desktop.
All content and source code for this package are subject to the terms of the MIT License.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.
- Universal Windows Platform apps
- Windows desktop apps
- Windows 11
- Windows 10
- Windows 8.1
- Windows 7 Service Pack 1
- Xbox One
- x86
- x64
- ARM64
- Visual Studio 2022
- Visual Studio 2019 (16.11)
- clang/LLVM v12 - v18
- MinGW 12.2, 13.2
- CMake 3.20