//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // The pointer's enter, move and leave as the Wayland backend reports them, // through Window::PointerEnter/PointerMove/PointerLeave — the helpers the // Win32 backend reports through as well. Drives the wl_pointer listener // callbacks directly, as MouseScroll does, so no compositor is needed, and // asserts: // // - onMouseEnter finds the entry position (in physical pixels) in // currentMousePos, and mouseInside set // - an enter is also a move: onMouseMove follows it, at the entry position // - onMouseLeave finds mouseInside cleared, and fires once per enter: a // second leave (sway, on input-device hot-plug) and a leave without an // enter fire nothing // - an enter listener that takes the window away stops the move that // would follow // // The Win32 WM_MOUSEMOVE/WM_MOUSELEAVE path calls the same helpers but // can't be exercised from a Linux test runner. #include #include import Crafter.Graphics; import Crafter.Event; import std; using namespace Crafter; namespace { int failures = 0; void Check(bool ok, std::string_view what) { std::println("{} {}", ok ? "PASS" : "FAIL", what); if (!ok) ++failures; } // A default-constructed window has no wl_surface, so a null surface is the // one PointerListenerHandleEnter finds it by. void Enter(double x, double y) { Device::PointerListenerHandleEnter(nullptr, nullptr, /*serial=*/1, /*surface=*/nullptr, wl_fixed_from_double(x), wl_fixed_from_double(y)); } void Motion(double x, double y) { Device::PointerListenerHandleMotion(nullptr, nullptr, /*time=*/0, wl_fixed_from_double(x), wl_fixed_from_double(y)); } void Leave() { Device::PointerListenerHandleLeave(nullptr, nullptr, /*serial=*/2, nullptr); } // An event as it fired, and what the window looked like then. struct Seen { std::string_view event; float x; float y; bool inside; bool operator==(const Seen&) const = default; }; } // namespace int main() { Window window; // default ctor creates no surface — safe without a compositor window.scale = 2.0f; Device::windows.push_back(&window); std::vector seen; auto record = [&](std::string_view event) { return [&seen, &window, event] { seen.push_back({ event, window.currentMousePos.x, window.currentMousePos.y, window.mouseInside }); }; }; EventListener enterSub(&window.onMouseEnter, record("enter")); EventListener moveSub(&window.onMouseMove, record("move")); EventListener leaveSub(&window.onMouseLeave, record("leave")); Enter(10.5, 20.25); Check(seen == std::vector{ { "enter", 21.0f, 40.5f, true }, { "move", 21.0f, 40.5f, true } }, "enter: entry position in currentMousePos, then a move to it"); Check(Device::focusedWindow == &window, "enter focuses the window"); seen.clear(); Motion(30.0, 5.0); Check(seen == std::vector{ { "move", 60.0f, 10.0f, true } }, "motion: a move"); seen.clear(); Enter(1.0, 1.0); Check(seen == std::vector{ { "move", 2.0f, 2.0f, true } }, "a second enter without a leave is only a move"); seen.clear(); Leave(); Check(seen == std::vector{ { "leave", 2.0f, 2.0f, false } }, "leave: mouseInside cleared before onMouseLeave"); Check(Device::focusedWindow == nullptr, "leave clears the focused window"); seen.clear(); Leave(); Device::focusedWindow = &window; // as touch_down sets it, with no enter Leave(); Check(seen.empty(), "a second leave, and a leave without an enter, fire nothing"); // ~Window clears focusedWindow, which is how the enter handler tells // that a listener destroyed the window before it reports the move. // Destroying it for real here would leave Event::Invoke reading the // destroyed event once the listener returns, so this listener clears // focusedWindow itself, as ~Window does. EventListener goneSub(&window.onMouseEnter, [] { Device::focusedWindow = nullptr; }); Enter(3.0, 4.0); Check(seen == std::vector{ { "enter", 6.0f, 8.0f, true } }, "no move after an enter listener takes the window away"); std::erase(Device::windows, &window); Device::focusedWindow = nullptr; if (failures != 0) { std::println("{} check(s) failed", failures); return EXIT_FAILURE; } std::println("all checks passed"); return EXIT_SUCCESS; }