Tutorial 11: Handling Mouse Input

CNA Tutorial Series  ·  Beginner

ℹ

What you’ll learn

  • Reading position, buttons and scroll wheel from MouseState.
  • Warping the pointer with Mouse::SetPosition() and toggling cursor visibility.

Before you start — Tutorial 10: Handling Keyboard Input — the mouse uses the same poll-and-compare pattern as the keyboard.

Mouse::GetState()

Mouse input follows the same snapshot-polling pattern as keyboard input. Call Mouse::GetState() once at the start of Update():

#include "Microsoft/Xna/Framework/Input/Mouse.hpp"

using namespace Microsoft::Xna::Framework::Input;

void MyGame::Update(GameTime& gameTime) {
    MouseState ms = Mouse::GetState();
    // Use 'ms' to read position, buttons, and scroll wheel
}

MouseState is a lightweight value type. Keeping the previous frame's state alongside the current one lets you detect click events (press) and release events.

MouseState Struct

MouseState exposes the following properties:

Property / MethodTypeDescription
getXProperty()intMouse X position in logical game coordinates (the window-client position mapped through the renderer; raw window pixels only when no transform applies). In relative mouse mode, the motion since the previous read
getYProperty()intMouse Y position in logical game coordinates, or the motion delta in relative mode
getLeftButtonProperty()ButtonStateLeft mouse button
getRightButtonProperty()ButtonStateRight mouse button
getMiddleButtonProperty()ButtonStateMiddle mouse button (scroll wheel click)
getXButton1Property()ButtonStateExtra mouse button 1 (side button)
getXButton2Property()ButtonStateExtra mouse button 2 (side button)
getScrollWheelValueProperty()intCumulative scroll wheel ticks (can be negative)

Mouse Position (X and Y)

Mouse position is returned in the game’s own (logical) coordinates — the same space as getViewportProperty() and your sprite positions: (0,0) is the top-left corner of the game area, with X increasing right and Y increasing down. CNA maps the pointer from the real window pixels through the active renderer, so the coordinates stay correct when the window is resized or letterboxed (letterboxing is the default presentation mode) as long as the renderer can report that mapping; if it cannot, you get raw window pixels.

void MyGame::Update(GameTime& gameTime) {
    MouseState ms = Mouse::GetState();

    int mouseX = ms.getXProperty();
    int mouseY = ms.getYProperty();

    // As a Vector2 for math:
    Vector2 mousePos(static_cast<float>(mouseX), static_cast<float>(mouseY));

    // As a Point (integer):
    Point mousePoint(ms.getXProperty(), ms.getYProperty());

    std::cout << "Mouse: " << mouseX << ", " << mouseY << "\n";
}

These are the coordinates you compare against sprite rectangles. If you apply a transform matrix to SpriteBatch::Begin() (a scrolling or zooming camera, for example), transform the mouse position through the inverse of that matrix to get world coordinates.

Button State

ButtonState is an enum with two values:

ButtonState::Pressed   // button is currently held down
ButtonState::Released  // button is not held

Hold detection (is the button currently down?)

MouseState ms = Mouse::GetState();

if (ms.getLeftButtonProperty() == ButtonState::Pressed) {
    // Left button is held — drag or continuous fire
    std::cout << "Left button held at " << ms.getXProperty() << ", " << ms.getYProperty() << "\n";
}

Click detection (single press event)

As with keyboard input, single-click events require comparing the current and previous state:

// Class members:
MouseState prevMs_;

// In Update():
MouseState ms = Mouse::GetState();

bool leftClicked  = (ms.getLeftButtonProperty()  == ButtonState::Pressed) &&
                    (prevMs_.getLeftButtonProperty() == ButtonState::Released);
bool rightClicked = (ms.getRightButtonProperty() == ButtonState::Pressed) &&
                    (prevMs_.getRightButtonProperty() == ButtonState::Released);

if (leftClicked) {
    // Fire once on click
    spawnProjectile(ms.getXProperty(), ms.getYProperty());
}
if (rightClicked) {
    // Right-click action
    openContextMenu(ms.getXProperty(), ms.getYProperty());
}

prevMs_ = ms;  // save for next frame

Hit testing: did the user click a rectangle?

Rectangle buttonRect(200, 250, 200, 60);  // x, y, w, h

bool clickedButton = leftClicked && buttonRect.Contains(Point(ms.getXProperty(), ms.getYProperty()));
if (clickedButton) {
    std::cout << "Button clicked!\n";
    startGame();
}

Rectangle::Contains(Point) returns true if the point is inside the rectangle (inclusive). Point(ms.getXProperty(), ms.getYProperty()) returns the mouse position as a Point.

ScrollWheelValue

getScrollWheelValueProperty() returns a cumulative counter of scroll wheel ticks. It increases when you scroll up and decreases when you scroll down. To detect scroll events, compare the current and previous value:

// Class member:
int prevScrollValue_ = 0;

// In Update():
MouseState ms = Mouse::GetState();
int scrollDelta = ms.getScrollWheelValueProperty() - prevScrollValue_;

if (scrollDelta > 0) {
    // Scrolled up — zoom in
    cameraZoom_ = std::min(cameraZoom_ + 0.1f, 3.0f);
} else if (scrollDelta < 0) {
    // Scrolled down — zoom out
    cameraZoom_ = std::max(cameraZoom_ - 0.1f, 0.5f);
}

prevScrollValue_ = ms.getScrollWheelValueProperty();

One notch of the scroll wheel corresponds to 120 units, XNA’s convention (the platform layer scales the wheel to it). Divide by 120 to get "notches scrolled":

int notches = scrollDelta / 120;  // positive = up, negative = down

CNA also offers a horizontal wheel as an extension, ms.getHorizontalScrollWheelValueEXTProperty(), scaled the same way. It is not part of XNA, so code that must stay XNA-portable should avoid it.

Mouse::SetPosition()

Mouse::SetPosition(x, y) moves the OS cursor to a specific position within the game window. This is useful for FPS-style games where you want to lock the cursor to the screen centre. In a browser the page cannot move the real cursor, so CNA emulates the warp: it anchors the requested position virtually and applies later physical movement to it, which keeps recentring mouse-look working there too:

// Lock the cursor to the window centre every frame
auto& vp = getGraphicsDeviceProperty().getViewportProperty();
int centreX = vp.getWidthProperty()  / 2;
int centreY = vp.getHeightProperty() / 2;

Mouse::SetPosition(centreX, centreY);

// Read the delta from this centre position
MouseState ms = Mouse::GetState();
// After SetPosition, GetState will return the new position immediately
// so store the delta before calling SetPosition

Typical FPS mouse-look pattern (the classic XNA approach, which works on every platform layer):

void MyGame::Update(GameTime& gameTime) {
    auto& vp = getGraphicsDeviceProperty().getViewportProperty();
    int cx = vp.getWidthProperty() / 2, cy = vp.getHeightProperty() / 2;

    MouseState ms = Mouse::GetState();

    // Compute delta from centre
    float dx = static_cast<float>(ms.getXProperty() - cx);
    float dy = static_cast<float>(ms.getYProperty() - cy);

    cameraYaw_   += dx * mouseSensitivity_;
    cameraPitch_ += dy * mouseSensitivity_;

    // Clamp pitch to prevent flipping
    cameraPitch_ = std::clamp(cameraPitch_, -MathHelper::PiOver2 + 0.01f,
                                             MathHelper::PiOver2 - 0.01f);

    // Reset cursor to centre
    Mouse::SetPosition(cx, cy);
}

As a CNA extension you can instead switch the pointer to relative mode with Mouse::setIsRelativeMouseModeEXTProperty(true). In that mode GetState() reports the motion since the previous read in getXProperty()/getYProperty() rather than an absolute position (and SetPosition does nothing), so no centring is needed.

Cursor Visibility

As in XNA, cursor visibility is a property of the Game, not of the Mouse class. Hide or show the OS cursor from inside your Game subclass:

// Hide the cursor (e.g., when showing a custom crosshair)
setIsMouseVisibleProperty(false);

// Show the cursor again (e.g., in menus)
setIsMouseVisibleProperty(true);

// Query current visibility
bool visible = getIsMouseVisibleProperty();

Common pattern: hide the cursor when entering gameplay and show it when the game is paused or in a menu.

Complete drag demo

class DragDemo final : public Game {
public:
    DragDemo() : graphics_(this) {}

protected:
    void LoadContent() override {
        spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
        pixel_ = std::make_unique<Texture2D>(getGraphicsDeviceProperty(), 1, 1);
        Color w = Color::White;
        pixel_->SetData(&w, 1);
    }

    void Update(GameTime& gameTime) override {
        MouseState ms = Mouse::GetState();
        Point mousePos(ms.getXProperty(), ms.getYProperty());

        bool lbDown = ms.getLeftButtonProperty() == ButtonState::Pressed;
        bool lbClick = lbDown && prevMs_.getLeftButtonProperty() == ButtonState::Released;

        Rectangle boxRect(static_cast<int>(boxX_), static_cast<int>(boxY_), 80, 80);

        // Start dragging on click inside the box
        if (lbClick && boxRect.Contains(mousePos)) {
            dragging_ = true;
            dragOffsetX_ = static_cast<float>(mousePos.X) - boxX_;
            dragOffsetY_ = static_cast<float>(mousePos.Y) - boxY_;
        }
        // Move box while dragging
        if (dragging_ && lbDown) {
            boxX_ = static_cast<float>(mousePos.X) - dragOffsetX_;
            boxY_ = static_cast<float>(mousePos.Y) - dragOffsetY_;
        }
        // Stop dragging on release
        if (!lbDown) dragging_ = false;

        if (Keyboard::GetState().IsKeyDown(Keys::Escape)) Exit();
        prevMs_ = ms;
    }

    void Draw(const GameTime&) override {
        auto& device = getGraphicsDeviceProperty();
        device.Clear(Color::CornflowerBlue);
        spriteBatch_->Begin();
        Color boxColor = dragging_ ? Color::Yellow : Color::Red;
        spriteBatch_->Draw(*pixel_,
            Rectangle(static_cast<int>(boxX_), static_cast<int>(boxY_), 80, 80),
            boxColor);
        spriteBatch_->End();
        // Game::EndDraw() presents the frame after Draw() returns.
    }

private:
    GraphicsDeviceManager graphics_;
    std::unique_ptr<SpriteBatch> spriteBatch_;
    std::unique_ptr<Texture2D> pixel_;
    MouseState prevMs_;
    float boxX_ = 360.0f, boxY_ = 260.0f;
    float dragOffsetX_ = 0, dragOffsetY_ = 0;
    bool dragging_ = false;
};

Click the red box to drag it around. It turns yellow while dragging. In Tutorial 12 you will combine input, delta time, and texture drawing into a proper moving sprite demo.