Added full clay border support to draw
This commit is contained in:
+373
-39
@@ -171,6 +171,7 @@ Global :: struct {
|
||||
|
||||
// -- Clay (once per frame in prepare_clay_batch) --
|
||||
clay_memory: [^]u8, // Raw memory block backing Clay's internal arena.
|
||||
clay_merge_open_stack: [dynamic]Clay_Merge_Candidate, // Pending Rectangle/Image primitives waiting for a matching Border to merge with.
|
||||
|
||||
// -- Text (occasional — font registration and text cache lookups) --
|
||||
text_cache: Text_Cache, // Font registry, SDL_ttf engine, and cached TTF_Text objects.
|
||||
@@ -381,6 +382,7 @@ init :: proc(
|
||||
),
|
||||
tmp_sub_batches = make([dynamic]Sub_Batch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_uncached_text = make([dynamic]^sdl_ttf.Text, 0, 16, allocator = allocator),
|
||||
clay_merge_open_stack = make([dynamic]Clay_Merge_Candidate, 0, 16, allocator = allocator),
|
||||
tmp_gaussian_blur_primitives = make(
|
||||
[dynamic]Gaussian_Blur_Primitive,
|
||||
0,
|
||||
@@ -482,6 +484,7 @@ clear_global :: proc() {
|
||||
clear(&GLOB.tmp_primitives)
|
||||
clear(&GLOB.tmp_sub_batches)
|
||||
clear(&GLOB.tmp_gaussian_blur_primitives)
|
||||
clear(&GLOB.clay_merge_open_stack)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
@@ -812,6 +815,29 @@ Backdrop_Marker :: struct {
|
||||
feather_ppx: f32,
|
||||
}
|
||||
|
||||
// One entry on the Clay merge stack. Pushed by `dispatch_clay_command` when emitting a
|
||||
// Rectangle or an Image primitive, then popped by a matching Border to retroactively add
|
||||
// the outline. See `try_dispatch_clay_border_merge` for the matching semantics.
|
||||
//INTERNAL
|
||||
Clay_Merge_Candidate :: struct {
|
||||
primitive_index: u32, // Index into `GLOB.tmp_primitives` of the candidate primitive.
|
||||
outer_bounds: Rectangle, // Clay's bounding box — keyed on for the bounds match check.
|
||||
corner_radii: clay.CornerRadius, // Clay's corner radii — also keyed on for the match check.
|
||||
image_data: Clay_Image_Data, // Only read when kind == .Fill_Texture (needed to refit UVs to inner_bounds).
|
||||
kind: Clay_Merge_Candidate_Kind,
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
Clay_Merge_Candidate_Kind :: enum u8 {
|
||||
// Solid Color brush. Used for Rectangle commands and for the bg primitive of an Image
|
||||
// command that has `backgroundColor.a > 0`. Merge mutation: shrink shape + add outline.
|
||||
Fill_Color,
|
||||
// Texture_Fill brush. Used for the image primitive of an Image command with no bg, where
|
||||
// `fit_params` returned `fit_rect == outer_bounds` (the image fully covers Clay's bounds).
|
||||
// Merge mutation: shrink shape + add outline + refit UV against inner_bounds.
|
||||
Fill_Texture,
|
||||
}
|
||||
|
||||
// Returns true if this Clay render command represents a backdrop primitive — i.e. its
|
||||
// `customData` points at a `Clay_Custom` whose active variant is `Backdrop_Marker`.
|
||||
is_clay_backdrop :: proc(cmd: ^clay.RenderCommand) -> bool {
|
||||
@@ -822,6 +848,263 @@ is_clay_backdrop :: proc(cmd: ^clay.RenderCommand) -> bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// Emit a Clay border drawn INSIDE `bounds` — the outer edge of each side aligns with
|
||||
// `bounds`, the inner edge is `border_width.*` pixels inset. Matches Clay's layout model
|
||||
// (CSS border-box) so the visible element occupies exactly Clay's allocated space.
|
||||
//
|
||||
// The fast path (uniform widths) uses `rectangle()` with the built-in SDF outline, which
|
||||
// always extends outward from the shape it's given — we pre-shrink the shape by
|
||||
// `border_width` so the outline lands precisely at Clay's bounds. The slow path (non-uniform
|
||||
// widths) emits per-side rectangles and per-corner arcs directly, all positioned inside
|
||||
// `bounds`. All-zero widths is a no-op.
|
||||
//
|
||||
// A corner is rounded iff its radius is positive AND both adjacent sides have positive
|
||||
// width. Top corners take their thickness from `border_width.top`, bottom corners from
|
||||
// `border_width.bottom`. When the two widths meeting at a corner differ there is a step at
|
||||
// the side/corner junction (acceptable for the rare mixed-width case).
|
||||
//
|
||||
// When `border_width > corner_radius`, the inner corner clamps to zero (sharp inside, still
|
||||
// rounded outside) — matches CSS-standard behavior.
|
||||
//INTERNAL
|
||||
clay_emit_partial_border :: proc(
|
||||
layer: ^Layer,
|
||||
bounds: Rectangle,
|
||||
border_color: Color,
|
||||
border_width: clay.BorderWidth,
|
||||
corner_radii: clay.CornerRadius,
|
||||
) {
|
||||
// All-zero: nothing to draw.
|
||||
if border_width.top == 0 && border_width.right == 0 && border_width.bottom == 0 && border_width.left == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Convert side widths once (u16 -> f32) and cache for reuse.
|
||||
width_top := f32(border_width.top)
|
||||
width_right := f32(border_width.right)
|
||||
width_bottom := f32(border_width.bottom)
|
||||
width_left := f32(border_width.left)
|
||||
|
||||
// Fast path: all four sides have the same nonzero width. Pre-shrink the shape by the
|
||||
// uniform width so the SDF outline (which always extends outward from the shape) lands
|
||||
// exactly at Clay's `bounds` — the visible border ends up INSIDE Clay's allocation while
|
||||
// the SDF mechanism keeps doing outward outlining. Single SDF primitive, exact curves,
|
||||
// analytical AA.
|
||||
if border_width.left == border_width.top &&
|
||||
border_width.top == border_width.right &&
|
||||
border_width.right == border_width.bottom {
|
||||
uniform_width := width_top
|
||||
inner_bounds := Rectangle {
|
||||
x = bounds.x + uniform_width,
|
||||
y = bounds.y + uniform_width,
|
||||
width = bounds.width - 2 * uniform_width,
|
||||
height = bounds.height - 2 * uniform_width,
|
||||
}
|
||||
inner_radii := Rectangle_Radii {
|
||||
top_left = max(0, corner_radii.topLeft - uniform_width),
|
||||
top_right = max(0, corner_radii.topRight - uniform_width),
|
||||
bottom_right = max(0, corner_radii.bottomRight - uniform_width),
|
||||
bottom_left = max(0, corner_radii.bottomLeft - uniform_width),
|
||||
}
|
||||
rectangle(
|
||||
layer,
|
||||
inner_bounds,
|
||||
BLANK,
|
||||
outline_color = border_color,
|
||||
outline_width = uniform_width,
|
||||
radii = inner_radii,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// A corner is drawn rounded only if its radius is positive AND both adjacent sides are present.
|
||||
top_left_rounded := corner_radii.topLeft > 0 && border_width.top > 0 && border_width.left > 0
|
||||
top_right_rounded := corner_radii.topRight > 0 && border_width.top > 0 && border_width.right > 0
|
||||
bottom_left_rounded := corner_radii.bottomLeft > 0 && border_width.bottom > 0 && border_width.left > 0
|
||||
bottom_right_rounded := corner_radii.bottomRight > 0 && border_width.bottom > 0 && border_width.right > 0
|
||||
|
||||
// Horizontal x-coordinates where the top/bottom side rectangles start/end. When the
|
||||
// adjacent corner is rounded, the side stops at `bounds.x + radius` (where the corner
|
||||
// arc takes over). When not rounded, the side runs to the bounds edge; the perpendicular
|
||||
// side handles the inset to avoid overlap.
|
||||
top_left_x: f32 = top_left_rounded ? bounds.x + corner_radii.topLeft : bounds.x
|
||||
top_right_x: f32 =
|
||||
top_right_rounded ? bounds.x + bounds.width - corner_radii.topRight : bounds.x + bounds.width
|
||||
bottom_left_x: f32 = bottom_left_rounded ? bounds.x + corner_radii.bottomLeft : bounds.x
|
||||
bottom_right_x: f32 =
|
||||
bottom_right_rounded ? bounds.x + bounds.width - corner_radii.bottomRight : bounds.x + bounds.width
|
||||
|
||||
// Vertical y-coordinates where the left/right side rectangles start/end. When the
|
||||
// adjacent corner is rounded, inset by the corner radius. When not rounded, inset by the
|
||||
// adjacent horizontal width — the horizontal side owns the corner area (extending through
|
||||
// it to the bounds edge), so the vertical side starts below it to avoid overdraw of
|
||||
// translucent colors.
|
||||
top_left_y: f32 = top_left_rounded ? bounds.y + corner_radii.topLeft : bounds.y + width_top
|
||||
top_right_y: f32 = top_right_rounded ? bounds.y + corner_radii.topRight : bounds.y + width_top
|
||||
bottom_left_y: f32 =
|
||||
bottom_left_rounded ? bounds.y + bounds.height - corner_radii.bottomLeft : bounds.y + bounds.height - width_bottom
|
||||
bottom_right_y: f32 =
|
||||
bottom_right_rounded ? bounds.y + bounds.height - corner_radii.bottomRight : bounds.y + bounds.height - width_bottom
|
||||
|
||||
// Side rectangles drawn INSIDE `bounds`. Sharp corners, solid fill, no outline. Each
|
||||
// gated on its own width — skipping zero-width sides saves the primitive upload.
|
||||
if border_width.top > 0 {
|
||||
top_side := Rectangle {
|
||||
x = top_left_x,
|
||||
y = bounds.y,
|
||||
width = top_right_x - top_left_x,
|
||||
height = width_top,
|
||||
}
|
||||
rectangle(layer, top_side, border_color)
|
||||
}
|
||||
if border_width.bottom > 0 {
|
||||
bottom_side := Rectangle {
|
||||
x = bottom_left_x,
|
||||
y = bounds.y + bounds.height - width_bottom,
|
||||
width = bottom_right_x - bottom_left_x,
|
||||
height = width_bottom,
|
||||
}
|
||||
rectangle(layer, bottom_side, border_color)
|
||||
}
|
||||
if border_width.left > 0 {
|
||||
left_side := Rectangle {
|
||||
x = bounds.x,
|
||||
y = top_left_y,
|
||||
width = width_left,
|
||||
height = bottom_left_y - top_left_y,
|
||||
}
|
||||
rectangle(layer, left_side, border_color)
|
||||
}
|
||||
if border_width.right > 0 {
|
||||
right_side := Rectangle {
|
||||
x = bounds.x + bounds.width - width_right,
|
||||
y = top_right_y,
|
||||
width = width_right,
|
||||
height = bottom_right_y - top_right_y,
|
||||
}
|
||||
rectangle(layer, right_side, border_color)
|
||||
}
|
||||
|
||||
// Corner arcs (90° quadrants) drawn INSIDE bounds: outer radius matches Clay's
|
||||
// `corner_radii`, inner radius is the outer radius minus the relevant border thickness
|
||||
// (clamped to 0 for thick borders — produces a filled pie slice when border > radius,
|
||||
// matching CSS). Angle convention matches ring(): 0° = +x (right), 90° = +y (down),
|
||||
// 180° = -x (left), 270° = -y (up).
|
||||
if top_left_rounded {
|
||||
radius := corner_radii.topLeft
|
||||
inner_radius := max(0, radius - width_top)
|
||||
center := Vec2{bounds.x + radius, bounds.y + radius}
|
||||
ring(layer, center, inner_radius, radius, border_color, start_angle = 180, end_angle = 270)
|
||||
}
|
||||
if top_right_rounded {
|
||||
radius := corner_radii.topRight
|
||||
inner_radius := max(0, radius - width_top)
|
||||
center := Vec2{bounds.x + bounds.width - radius, bounds.y + radius}
|
||||
ring(layer, center, inner_radius, radius, border_color, start_angle = 270, end_angle = 360)
|
||||
}
|
||||
if bottom_right_rounded {
|
||||
radius := corner_radii.bottomRight
|
||||
inner_radius := max(0, radius - width_bottom)
|
||||
center := Vec2{bounds.x + bounds.width - radius, bounds.y + bounds.height - radius}
|
||||
ring(layer, center, inner_radius, radius, border_color, start_angle = 0, end_angle = 90)
|
||||
}
|
||||
if bottom_left_rounded {
|
||||
radius := corner_radii.bottomLeft
|
||||
inner_radius := max(0, radius - width_bottom)
|
||||
center := Vec2{bounds.x + radius, bounds.y + bounds.height - radius}
|
||||
ring(layer, center, inner_radius, radius, border_color, start_angle = 90, end_angle = 180)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to retroactively merge this Border into a pending Rectangle/Image candidate on the
|
||||
// merge stack. Returns true on success so the caller can skip the standalone Border emission.
|
||||
//
|
||||
// Clay emits a parent element's bg and border bracketing all the children's commands, so a
|
||||
// simple "is the next command a Border?" check (the previous approach) only catches leaf
|
||||
// elements. The stack approach lets us pair them across arbitrary nesting: every Rectangle/
|
||||
// Image push registers itself; every Border pops down until it finds a geometric match.
|
||||
//
|
||||
// Pop semantics: non-matching candidates above the match are discarded — their elements had
|
||||
// no border anyway, so their primitives stay in `tmp_primitives` as plain Rectangles. A
|
||||
// Border that finds no match at all falls back to standalone `clay_emit_partial_border`.
|
||||
//
|
||||
// Predicates that decline a candidate:
|
||||
// - non-uniform or zero border widths (can't be a single uniform outline)
|
||||
// - translucent border (the unmerged path's bg-under-border blending differs)
|
||||
// - mismatched bounds or cornerRadius (the candidate isn't from the same element)
|
||||
//
|
||||
// False-match risk: two unrelated elements with bit-identical bounds and corner radii.
|
||||
// Requires geometric coincidence (rare in practice), and even when it fires, the misattributed
|
||||
// outline still lands at the correct screen position with the correct color — the pixels
|
||||
// match the unmerged ground truth for opaque borders (the only kind we merge).
|
||||
//INTERNAL
|
||||
try_dispatch_clay_border_merge :: proc(bounds: Rectangle, border_data: clay.BorderRenderData) -> bool {
|
||||
border_width := border_data.width
|
||||
uniform_nonzero :=
|
||||
border_width.left == border_width.top &&
|
||||
border_width.top == border_width.right &&
|
||||
border_width.right == border_width.bottom &&
|
||||
border_width.top > 0
|
||||
if !uniform_nonzero do return false
|
||||
if border_data.color[3] < 255 do return false
|
||||
|
||||
for len(GLOB.clay_merge_open_stack) > 0 {
|
||||
candidate := pop(&GLOB.clay_merge_open_stack)
|
||||
if candidate.outer_bounds != bounds do continue
|
||||
if candidate.corner_radii != border_data.cornerRadius do continue
|
||||
apply_clay_border_merge_to_primitive(candidate, border_data)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Mutates `tmp_primitives[candidate.primitive_index]` in place: shrinks the SDF shape by
|
||||
// the uniform border width so the (outward) outline lands at the outer bounds, sets the
|
||||
// outline flag and params, and — for `Fill_Texture` candidates — refits the texture's UV
|
||||
// against `inner_bounds` so the image doesn't overflow into the border strip.
|
||||
//
|
||||
// The primitive's `bounds` field stays at the outer bounds: the rasterized quad already
|
||||
// covers the area the outline now occupies. Skipping the bounds expansion that
|
||||
// `apply_brush_and_outline` would normally do is intentional — expanding here would push the
|
||||
// rasterized quad past Clay's outer edge.
|
||||
//INTERNAL
|
||||
apply_clay_border_merge_to_primitive :: proc(
|
||||
candidate: Clay_Merge_Candidate,
|
||||
border_data: clay.BorderRenderData,
|
||||
) {
|
||||
prim := &GLOB.tmp_primitives[candidate.primitive_index]
|
||||
uniform_width := f32(border_data.width.top)
|
||||
dpi_scale := GLOB.dpi_scaling
|
||||
|
||||
inner_half_width := candidate.outer_bounds.width * 0.5 - uniform_width
|
||||
inner_half_height := candidate.outer_bounds.height * 0.5 - uniform_width
|
||||
prim.params.rrect.half_size_ppx = {inner_half_width * dpi_scale, inner_half_height * dpi_scale}
|
||||
prim.params.rrect.radii_ppx = {
|
||||
max(0, candidate.corner_radii.topLeft - uniform_width) * dpi_scale,
|
||||
max(0, candidate.corner_radii.topRight - uniform_width) * dpi_scale,
|
||||
max(0, candidate.corner_radii.bottomRight - uniform_width) * dpi_scale,
|
||||
max(0, candidate.corner_radii.bottomLeft - uniform_width) * dpi_scale,
|
||||
}
|
||||
|
||||
// Set the outline bit in the packed flags field (low byte = Shape_Kind, bits 8+ = Shape_Flags).
|
||||
prim.flags |= u32(transmute(u8)Shape_Flags{.Outline}) << 8
|
||||
prim.effects.outline_color = color_from_clay(border_data.color)
|
||||
prim.effects.outline_packed = pack_f16_pair(f16(uniform_width * dpi_scale), 0)
|
||||
|
||||
if candidate.kind == .Fill_Texture {
|
||||
// The candidate was only pushed if its `fit_rect == outer_bounds` at emission time, so the
|
||||
// image fills the rasterized quad. Refit UVs against `inner_bounds` so the image is scoped
|
||||
// to the area inside the new outline rather than overflowing into the border strip.
|
||||
inner_bounds := Rectangle {
|
||||
x = candidate.outer_bounds.x + uniform_width,
|
||||
y = candidate.outer_bounds.y + uniform_width,
|
||||
width = candidate.outer_bounds.width - 2 * uniform_width,
|
||||
height = candidate.outer_bounds.height - 2 * uniform_width,
|
||||
}
|
||||
uv_rect, _, _ := fit_params(candidate.image_data.fit, inner_bounds, candidate.image_data.texture_id)
|
||||
prim.uv_rect = {uv_rect.x, uv_rect.y, uv_rect.width, uv_rect.height}
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch a single non-backdrop Clay render command to the appropriate `draw` primitive.
|
||||
// Extracted from the main `prepare_clay_batch` walk so that the deferred-buffer flush path
|
||||
// can replay commands accumulated during an open backdrop scope without duplicating the
|
||||
@@ -864,30 +1147,61 @@ dispatch_clay_command :: proc(
|
||||
render_data := render_command.renderData.image
|
||||
if render_data.imageData == nil do return
|
||||
img_data := (^Clay_Image_Data)(render_data.imageData)^
|
||||
cr := render_data.cornerRadius
|
||||
corner_radii_clay := render_data.cornerRadius
|
||||
radii := Rectangle_Radii {
|
||||
top_left = cr.topLeft,
|
||||
top_right = cr.topRight,
|
||||
bottom_right = cr.bottomRight,
|
||||
bottom_left = cr.bottomLeft,
|
||||
top_left = corner_radii_clay.topLeft,
|
||||
top_right = corner_radii_clay.topRight,
|
||||
bottom_right = corner_radii_clay.bottomRight,
|
||||
bottom_left = corner_radii_clay.bottomLeft,
|
||||
}
|
||||
|
||||
// Background color behind the image (Clay allows it)
|
||||
bg := color_from_clay(render_data.backgroundColor)
|
||||
if bg.a > 0 {
|
||||
rectangle(layer, bounds, bg, radii = radii)
|
||||
background_color := color_from_clay(render_data.backgroundColor)
|
||||
uv_rect, sampler, fit_rect := fit_params(img_data.fit, bounds, img_data.texture_id)
|
||||
|
||||
if background_color.a > 0 {
|
||||
// Bg behind image. Push the bg primitive as the merge candidate so a matching Border
|
||||
// turns into a bg+border-merged primitive plus a separate image draw on top.
|
||||
rectangle(layer, bounds, background_color, radii = radii)
|
||||
bg_primitive_index := u32(len(GLOB.tmp_primitives) - 1)
|
||||
rectangle(
|
||||
layer,
|
||||
fit_rect,
|
||||
Texture_Fill{id = img_data.texture_id, tint = img_data.tint, uv_rect = uv_rect, sampler = sampler},
|
||||
radii = radii,
|
||||
)
|
||||
append(
|
||||
&GLOB.clay_merge_open_stack,
|
||||
Clay_Merge_Candidate {
|
||||
primitive_index = bg_primitive_index,
|
||||
outer_bounds = bounds,
|
||||
corner_radii = corner_radii_clay,
|
||||
kind = .Fill_Color,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
// No bg: the image itself can host the outline if its fit fully covers Clay's bounds.
|
||||
// `Fit_Mode.Fit` with aspect mismatch returns a sub-rect, which can't host an outline
|
||||
// (the rasterized quad wouldn't reach Clay's outer edge), so we skip pushing.
|
||||
rectangle(
|
||||
layer,
|
||||
fit_rect,
|
||||
Texture_Fill{id = img_data.texture_id, tint = img_data.tint, uv_rect = uv_rect, sampler = sampler},
|
||||
radii = radii,
|
||||
)
|
||||
if fit_rect == bounds {
|
||||
img_primitive_index := u32(len(GLOB.tmp_primitives) - 1)
|
||||
append(
|
||||
&GLOB.clay_merge_open_stack,
|
||||
Clay_Merge_Candidate {
|
||||
primitive_index = img_primitive_index,
|
||||
outer_bounds = bounds,
|
||||
corner_radii = corner_radii_clay,
|
||||
image_data = img_data,
|
||||
kind = .Fill_Texture,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Compute fit UVs
|
||||
uv, sampler, inner := fit_params(img_data.fit, bounds, img_data.texture_id)
|
||||
|
||||
// Draw the image
|
||||
rectangle(
|
||||
layer,
|
||||
inner,
|
||||
Texture_Fill{id = img_data.texture_id, tint = img_data.tint, uv_rect = uv, sampler = sampler},
|
||||
radii = radii,
|
||||
)
|
||||
case clay.RenderCommandType.ScissorStart:
|
||||
if bounds.width == 0 || bounds.height == 0 do return
|
||||
|
||||
@@ -919,29 +1233,38 @@ dispatch_clay_command :: proc(
|
||||
unimplemented("Clay overlays not supported yet...")
|
||||
case clay.RenderCommandType.Rectangle:
|
||||
render_data := render_command.renderData.rectangle
|
||||
cr := render_data.cornerRadius
|
||||
color := color_from_clay(render_data.backgroundColor)
|
||||
corner_radii_clay := render_data.cornerRadius
|
||||
background_color := color_from_clay(render_data.backgroundColor)
|
||||
radii := Rectangle_Radii {
|
||||
top_left = cr.topLeft,
|
||||
top_right = cr.topRight,
|
||||
bottom_right = cr.bottomRight,
|
||||
bottom_left = cr.bottomLeft,
|
||||
top_left = corner_radii_clay.topLeft,
|
||||
top_right = corner_radii_clay.topRight,
|
||||
bottom_right = corner_radii_clay.bottomRight,
|
||||
bottom_left = corner_radii_clay.bottomLeft,
|
||||
}
|
||||
|
||||
rectangle(layer, bounds, color, radii = radii)
|
||||
rectangle(layer, bounds, background_color, radii = radii)
|
||||
// Register this primitive as a merge candidate. If the element has a matching Border
|
||||
// later in the stream (after its children's commands), `try_dispatch_clay_border_merge`
|
||||
// will pop this candidate and mutate the primitive in-place to add the outline.
|
||||
primitive_index := u32(len(GLOB.tmp_primitives) - 1)
|
||||
append(
|
||||
&GLOB.clay_merge_open_stack,
|
||||
Clay_Merge_Candidate {
|
||||
primitive_index = primitive_index,
|
||||
outer_bounds = bounds,
|
||||
corner_radii = corner_radii_clay,
|
||||
kind = .Fill_Color,
|
||||
},
|
||||
)
|
||||
case clay.RenderCommandType.Border:
|
||||
render_data := render_command.renderData.border
|
||||
cr := render_data.cornerRadius
|
||||
color := color_from_clay(render_data.color)
|
||||
thickness := f32(render_data.width.top)
|
||||
radii := Rectangle_Radii {
|
||||
top_left = cr.topLeft,
|
||||
top_right = cr.topRight,
|
||||
bottom_right = cr.bottomRight,
|
||||
bottom_left = cr.bottomLeft,
|
||||
}
|
||||
|
||||
rectangle(layer, bounds, BLANK, outline_color = color, outline_width = thickness, radii = radii)
|
||||
if try_dispatch_clay_border_merge(bounds, render_data) do return
|
||||
clay_emit_partial_border(
|
||||
layer,
|
||||
bounds,
|
||||
color_from_clay(render_data.color),
|
||||
render_data.width,
|
||||
render_data.cornerRadius,
|
||||
)
|
||||
case clay.RenderCommandType.Custom:
|
||||
// Copy the CustomRenderData by value so we can patch its `customData` field for the
|
||||
// user callback without mutating Clay-owned memory. After unwrapping, the callback
|
||||
@@ -1021,6 +1344,10 @@ flush_deferred_and_close_backdrop_scope :: proc(
|
||||
end_backdrop(layer)
|
||||
backdrop_scope_open^ = false
|
||||
}
|
||||
// Clear the merge stack at scope/stratum boundaries: any pending candidates from the
|
||||
// pre-scope (or pre-transition) commands stay as plain primitives — they can't merge
|
||||
// with Borders on the far side of the boundary because that would change draw order.
|
||||
clear(&GLOB.clay_merge_open_stack)
|
||||
for index in deferred_indices^ {
|
||||
cmd := clay.RenderCommandArray_Get(&batch.cmds, index)
|
||||
dispatch_clay_command(layer, cmd, custom_draw, temp_allocator)
|
||||
@@ -1069,6 +1396,10 @@ prepare_clay_batch :: proc(
|
||||
// the previous batch already saw.
|
||||
previous_z_index := GLOB.clay_z_index
|
||||
|
||||
// Start with a clean merge stack. The stack is also cleared by
|
||||
// `flush_deferred_and_close_backdrop_scope` at every stratum boundary; both clears together
|
||||
// ensure merge candidates never pair across a boundary that would shift draw order.
|
||||
clear(&GLOB.clay_merge_open_stack)
|
||||
for i in 0 ..< command_count {
|
||||
cmd := clay.RenderCommandArray_Get(&batch.cmds, i32(i))
|
||||
|
||||
@@ -1098,6 +1429,9 @@ prepare_clay_batch :: proc(
|
||||
} else if backdrop_scope_open {
|
||||
append(&deferred_indices, i32(i))
|
||||
} else {
|
||||
// Rectangle/Image dispatches push merge candidates; Border dispatches pop the stack
|
||||
// to retroactively add an outline to a matching candidate. See
|
||||
// `try_dispatch_clay_border_merge` for the matching semantics.
|
||||
dispatch_clay_command(layer, cmd, custom_draw, temp_allocator)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user