In this article we're going to implement mouse motion input. We'll write 2 new functions, _WindowInputEvent
and ElementFindByPoint
.
The control flow will look something like this:
cursorX
and cursorY
in the Window
structure with the new position of the cursor._WindowInputEvent
with details of a MSG_MOUSE_MOVE
message._WindowInputEvent
finds the element over which the mouse cursor is hovering using ElementFindByPoint
_WindowInputEvent
sends the MSG_MOUSE_MOVE
message to this element._WindowInputEvent
calls _Update
so that anything that was queued for repainting in step 5. will be repainted.Let's start by implementing ElementFindByPoint
. This function takes an element and a point. It looks for the deepest descendent element takes contains the point (recall that sibling elements cannot overlap so such element will be unique if it exists), and returns it, or if no descendent contains the point, it returns the pointer to the original element. Unsurprisingly, as we are operating on the element hierarchy, this function works recursively.
Element *ElementFindByPoint(Element *element, int x, int y) {
for (uintptr_t i = 0; i < element->childCount; i++) {
if (RectangleContains(element->children[i]->clip, x, y)) {
return ElementFindByPoint(element->children[i], x, y);
}
}
return element;
}
Now, we add MSG_MOUSE_MOVE
to the Message
enumeration, and we add int cursorX, cursorY;
to the Window
structure. And thus we're ready to write _WindowInputEvent
. This function will receive all input events from the platform layer, but for this article it only has to deal with MSG_MOUSE_MOVE
.
void _WindowInputEvent(Window *window, Message message, int di, void *dp) {
// Find the element the mouse cursor is hovering over.
Element *hovered = ElementFindByPoint(&window->e, window->cursorX, window->cursorY);
// If this is a mouse move message, send the message to the hovered element.
if (message == MSG_MOUSE_MOVE) {
ElementMessage(hovered, MSG_MOUSE_MOVE, di, dp);
}
// Process any queued repaints.
_Update();
}
In the platform layers we now add the code to update cursorX
and cursorY
and then call _WindowInputEvent
. In typical fashion, I'm not going to go through this code.
Okay, this will all work as-is, but there's one little extra feature I want to add. Typically, it is nice to be able to check quickly which element the mouse cursor is hovering over, so we'll store a pointer to such element in the Window
structure. Furthermore, when this element changes (that is, when the mouse cursor moves from one element to another), we'll send a MSG_UPDATE
message to the old and new hovered elements. In the Message
enumeration, we add MSG_UPDATE
, and in the Window
structure we add Element *hovered;
. We also define the constant #define UPDATE_HOVERED (1)
. This will be passed in the int di
parameter of the MSG_UPDATE
message, indicating that it was the hovered status of the element that was modified. We'll define more constants as needed as we use the MSG_UPDATE
message to inform elements of more things. Finally, we add the logic of processing the hovered element into _WindowInputEvent
:
void _WindowInputEvent(Window *window, Message message, int di, void *dp) {
Element *hovered = ElementFindByPoint(&window->e, window->cursorX, window->cursorY);
if (message == MSG_MOUSE_MOVE) {
ElementMessage(hovered, MSG_MOUSE_MOVE, di, dp);
}
if (hovered != window->hovered) {
Element *previous = window->hovered;
window->hovered = hovered;
ElementMessage(previous, MSG_UPDATE, UPDATE_HOVERED, 0);
ElementMessage(window->hovered, MSG_UPDATE, UPDATE_HOVERED, 0);
}
_Update();
}
The test usage code for this article creates two elements, both of which print out all the MSG_MOUSE_MOVE
and MSG_UPDATE
messages they receive. Observe how when you move your cursor from one element to the other they are both sent MSG_MOUSE_MOVE
messages, and how when you move your cursor outside the window the Window
element at the root of hierarchy becomes the hovered element.