Problem
Mouse input coordinates and the Pencuil viewport are expressed in two different coordinate spaces, which agree today only because high pixel density is never requested.
SDL3 reports mouse coordinates in window coordinates (logical points): SDL_MouseMotionEvent.x/y, SDL_MouseButtonEvent.x/y, and SDL_MouseWheelEvent.mouse_x/mouse_y. MouseService passes these through unconverted, and PencilSystem feeds them straight into Pencil.UpdateCursor((Vector2Int)args.Position).
Pencil, meanwhile, works in render pixels. PencilSystem.Update sizes the viewport from Window.RenderSizeInPixels (SDL_GetWindowSizeInPixels, src/Pixely/Window.cs:59), and PencuilRenderer builds its projection from the same value. Every hit test in Pencil — hover areas, click tests, hover patches, scroll areas — compares point-space cursor positions against pixel-space rectangles.
The two spaces are separate SDL concepts: Window.Size uses SDL_GetWindowSize (points) while Window.RenderSizeInPixels uses SDL_GetWindowSizeInPixels (pixels).
Why it does not reproduce today
PixelyFactory never sets SDL_WINDOW_HIGH_PIXEL_DENSITY (src/Pixely/PixelyFactory.cs:128-162) and WindowConfig exposes no option for it. Without that flag SDL keeps the window in low-DPI mode, so SDL_GetWindowSizeInPixels matches SDL_GetWindowSize and the mismatch cancels out.
This is therefore a latent defect, not a user-visible one. It becomes real as soon as SDL_WINDOW_HIGH_PIXEL_DENSITY is supported, on a 2x display every cursor position would be reported at half the coordinates the GUI is laid out in, so hit testing would be offset and wrong across the whole window.
Proposed direction
Convert mouse coordinates into render pixels at the boundary rather than at each consumer, so a single conversion covers hit testing, wheel routing, and any future pointer consumer. The scale factor is the ratio between SDL_GetWindowSizeInPixels and SDL_GetWindowSize (or SDL_GetWindowPixelDensity).
Doing this before high pixel density support lands keeps the change independent of the DPI work itself.
Acceptance criteria
- Mouse event positions delivered to consumers are in the same coordinate space as
Window.RenderSizeInPixels.
- Pencuil hit testing stays correct when the window pixel size differs from its point size.
- The conversion happens once at the input boundary rather than per consumer.
Problem
Mouse input coordinates and the Pencuil viewport are expressed in two different coordinate spaces, which agree today only because high pixel density is never requested.
SDL3 reports mouse coordinates in window coordinates (logical points):
SDL_MouseMotionEvent.x/y,SDL_MouseButtonEvent.x/y, andSDL_MouseWheelEvent.mouse_x/mouse_y.MouseServicepasses these through unconverted, andPencilSystemfeeds them straight intoPencil.UpdateCursor((Vector2Int)args.Position).Pencil, meanwhile, works in render pixels.PencilSystem.Updatesizes the viewport fromWindow.RenderSizeInPixels(SDL_GetWindowSizeInPixels,src/Pixely/Window.cs:59), andPencuilRendererbuilds its projection from the same value. Every hit test inPencil— hover areas, click tests, hover patches, scroll areas — compares point-space cursor positions against pixel-space rectangles.The two spaces are separate SDL concepts:
Window.SizeusesSDL_GetWindowSize(points) whileWindow.RenderSizeInPixelsusesSDL_GetWindowSizeInPixels(pixels).Why it does not reproduce today
PixelyFactorynever setsSDL_WINDOW_HIGH_PIXEL_DENSITY(src/Pixely/PixelyFactory.cs:128-162) andWindowConfigexposes no option for it. Without that flag SDL keeps the window in low-DPI mode, soSDL_GetWindowSizeInPixelsmatchesSDL_GetWindowSizeand the mismatch cancels out.This is therefore a latent defect, not a user-visible one. It becomes real as soon as
SDL_WINDOW_HIGH_PIXEL_DENSITYis supported, on a 2x display every cursor position would be reported at half the coordinates the GUI is laid out in, so hit testing would be offset and wrong across the whole window.Proposed direction
Convert mouse coordinates into render pixels at the boundary rather than at each consumer, so a single conversion covers hit testing, wheel routing, and any future pointer consumer. The scale factor is the ratio between
SDL_GetWindowSizeInPixelsandSDL_GetWindowSize(orSDL_GetWindowPixelDensity).Doing this before high pixel density support lands keeps the change independent of the DPI work itself.
Acceptance criteria
Window.RenderSizeInPixels.