Add rounded-rect fill, arc stroke and clip-rect drawing primitives
The UI subsystem's render executor needs all three -- clay emits rectangles with corner radii, per-side borders, and scissor commands -- but they earn their place in draw.h on the same terms as everything else there: a rounded panel, a ring gauge and a clipped viewport are things a game wants with or without a layout engine. akgl_draw_filled_rounded_rect decomposes into three band fills plus four quarter-circle triangle fans through SDL_RenderGeometry, with the radius clamped to half the shorter side. akgl_draw_arc strokes between an outer and inner radius as one triangle strip, stepping in proportion to the swept angle under a fixed vertex cap -- no VLAs, unlike clay's own SDL3 reference renderer. akgl_draw_set_clip wraps SDL_SetRenderClipRect and is the one draw entry point where a NULL rectangle is legal, because that is how a clip is cleared. Pixel-readback tests cover the corner geometry (inside the arc filled, outside it untouched), the radius clamp, the zero-radius fall-through, ring and quarter-arc coverage at mid-stroke, sweep bounds, and clip set/clear round trips. Co-Authored-By: Claude Code (Claude Fable 5, claude-fable-5) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
This commit is contained in:
@@ -36,6 +36,25 @@
|
||||
*/
|
||||
#define AKGL_DRAW_MAX_FLOOD_SPANS 4096
|
||||
|
||||
/**
|
||||
* @brief Triangles each corner of akgl_draw_filled_rounded_rect() is built from.
|
||||
*
|
||||
* A quarter circle approximated by this many segments is visually round at any
|
||||
* radius a UI panel plausibly uses; the vertex arrays are fixed at this size,
|
||||
* so it is a compile-time cost, not a per-call one.
|
||||
*/
|
||||
#define AKGL_DRAW_ROUNDED_RECT_SEGMENTS 16
|
||||
|
||||
/**
|
||||
* @brief Most vertices akgl_draw_arc() will spend on one arc.
|
||||
*
|
||||
* An arc is a triangle strip between its inner and outer edge, two vertices
|
||||
* per step, so half of these are positions along the curve. The step count
|
||||
* scales with the swept angle and is clamped to fit this array -- a full
|
||||
* circle gets all of them, a sliver gets a few -- rather than allocating.
|
||||
*/
|
||||
#define AKGL_DRAW_ARC_MAX_POINTS 64
|
||||
|
||||
/**
|
||||
* @brief Paint an 8x8 grey checkerboard over a region, the way an image editor shows transparency.
|
||||
*
|
||||
@@ -242,4 +261,86 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_copy_region(akgl_RenderBackend *sel
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_paste_region(akgl_RenderBackend *self, SDL_Surface *src, float32_t x, float32_t y);
|
||||
|
||||
/**
|
||||
* @brief Fill a rectangle whose corners are rounded off with quarter circles.
|
||||
*
|
||||
* The body is three axis-aligned fills and the corners are triangle fans
|
||||
* through `SDL_RenderGeometry` -- #AKGL_DRAW_ROUNDED_RECT_SEGMENTS triangles
|
||||
* each -- so nothing here is anti-aliased, matching every other primitive in
|
||||
* this header.
|
||||
*
|
||||
* @param self The backend to draw through. Required, along with its
|
||||
* `sdl_renderer`.
|
||||
* @param rect The rectangle, in render-target pixels. Required. A zero or
|
||||
* negative width or height fills nothing and is not reported.
|
||||
* @param radius Corner radius in pixels. Zero or negative falls through to
|
||||
* akgl_draw_filled_rect() -- a square corner is not an error.
|
||||
* Larger than half the shorter side is clamped to it, which at
|
||||
* the limit turns a square into a circle rather than folding the
|
||||
* corners over each other.
|
||||
* @param color Colour to fill with, including alpha.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, or @p rect is
|
||||
* `NULL`.
|
||||
* @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if a band
|
||||
* or corner cannot be drawn. A failure partway leaves the shape
|
||||
* partially drawn.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_filled_rounded_rect(akgl_RenderBackend *self, SDL_FRect *rect, float32_t radius, SDL_Color color);
|
||||
|
||||
/**
|
||||
* @brief Stroke a circular arc of a given thickness between two angles.
|
||||
*
|
||||
* Angles are in degrees, 0 pointing along +x (three o'clock) and increasing
|
||||
* clockwise on screen -- the same convention as the rotation argument the
|
||||
* render backend's `draw_texture` takes. The stroke runs from the arc's outer
|
||||
* edge at @p radius inward by @p thickness, drawn as one triangle strip, not
|
||||
* anti-aliased.
|
||||
*
|
||||
* @param self The backend to draw through. Required, along with its
|
||||
* `sdl_renderer`.
|
||||
* @param x Horizontal position of the arc's centre.
|
||||
* @param y Vertical position of the arc's centre.
|
||||
* @param radius Outer radius in pixels. Must be positive.
|
||||
* @param start_deg Angle the arc starts at.
|
||||
* @param end_deg Angle the arc ends at. At or below @p start_deg nothing is
|
||||
* drawn and nothing is reported, matching the zero-area
|
||||
* rectangle fills above. Neither angle is normalized, so an
|
||||
* arc crossing three o'clock is 350 to 370, not 350 to 10; a
|
||||
* sweep beyond 360 degrees is clamped to one full turn.
|
||||
* @param thickness Stroke width in pixels, measured inward from @p radius.
|
||||
* Must be positive; at or above @p radius the arc becomes a
|
||||
* filled wedge to the centre.
|
||||
* @param color Colour to stroke in, including alpha.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`.
|
||||
* @throws AKERR_OUTOFBOUNDS If @p radius or @p thickness is not positive. The
|
||||
* message reports the offending value.
|
||||
* @throws AKGL_ERR_SDL If the strip cannot be drawn.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_arc(akgl_RenderBackend *self, float32_t x, float32_t y, float32_t radius, float32_t start_deg, float32_t end_deg, float32_t thickness, SDL_Color color);
|
||||
|
||||
/**
|
||||
* @brief Restrict every subsequent draw to a rectangle of the render target.
|
||||
*
|
||||
* Everything drawn through @p self after this -- primitives here, textures,
|
||||
* text -- is clipped to @p rect until the restriction is cleared. This is the
|
||||
* only entry point in this header where a `NULL` rectangle is legal rather
|
||||
* than an error: it means "clear the restriction", matching what
|
||||
* `SDL_SetRenderClipRect` does with it, and there is no other value that
|
||||
* could.
|
||||
*
|
||||
* The clip is renderer state, not saved and restored around anything: a
|
||||
* caller that sets it owns putting it back, the way a `CLEANUP` block puts
|
||||
* back a draw colour. Leaving it set clips the next frame's world too.
|
||||
*
|
||||
* @param self The backend to clip. Required, along with its `sdl_renderer`.
|
||||
* @param rect Rectangle to clip to, in render-target pixels -- integer, since
|
||||
* a clip boundary is a pixel boundary. `NULL` clears the clip.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`.
|
||||
* @throws AKGL_ERR_SDL If the clip cannot be set or cleared.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_set_clip(akgl_RenderBackend *self, SDL_Rect *rect);
|
||||
|
||||
#endif //_AKGL_DRAW_H_
|
||||
|
||||
Reference in New Issue
Block a user