Skip to content

Commit 4b35dc4

Browse files
committed
fix: stack clip regions so nested clips restore the parent rect
Clip state was a single rect, so closing an inner clip turned clipping off entirely and later siblings leaked past the outer bounds. Replace it with a fixed-depth stack: SCISSOR_START pushes intersect(parent, child) and SCISSOR_END pops, restoring the parent rect while the stack is non-empty. The active top mirrors into the existing clipx/clipy/clipw/cliph scalars, so setcell is unchanged. Fixes #77
1 parent 1159e3e commit 4b35dc4

1 file changed

Lines changed: 54 additions & 7 deletions

File tree

src/clayterm.c

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,26 @@
3838

3939
#define MAX_ERRORS 32
4040

41+
/* clip stack depth: nesting beyond this clamps to the deepest rect */
42+
#define CLIP_STACK_MAX 16
43+
44+
typedef struct {
45+
int x, y, w, h;
46+
} ClipRect;
47+
4148
struct Clayterm {
4249
int w, h;
4350
Cell *front;
4451
Cell *back;
4552
Buffer out;
4653
uint32_t lastfg, lastbg;
4754
int lastx, lasty;
48-
/* clip region */
55+
/* clip region (active top mirrored here so setcell stays unchanged) */
4956
int clipx, clipy, clipw, cliph;
5057
int clipping;
58+
/* clip stack: nesting pushes intersected rects, leaving pops to restore */
59+
ClipRect clipstack[CLIP_STACK_MAX];
60+
int clipdepth;
5161
/* error collection */
5262
Clay_ErrorData errors[MAX_ERRORS];
5363
int error_count;
@@ -596,6 +606,8 @@ void reduce(struct Clayterm *ct, uint32_t *buf, int len, int mode, int row) {
596606
ct->out.length = 0;
597607
ct->lastfg = ct->lastbg = 0xffffffff;
598608
ct->lastx = ct->lasty = -1;
609+
ct->clipdepth = 0;
610+
ct->clipping = 0;
599611

600612
cells_fill(ct->back, ct->w, ct->h, ' ', ATTR_DEFAULT, ATTR_DEFAULT);
601613

@@ -618,15 +630,50 @@ void reduce(struct Clayterm *ct, uint32_t *buf, int len, int mode, int row) {
618630
case CLAY_RENDER_COMMAND_TYPE_BORDER:
619631
render_border(ct, x0, y0, x1, y1, &cmd->renderData.border);
620632
break;
621-
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START:
633+
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
634+
/* intersect the child box with the current active rect (if any) */
635+
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1;
636+
if (ct->clipdepth > 0) {
637+
ClipRect top = ct->clipstack[ct->clipdepth - 1];
638+
if (top.x > nx0)
639+
nx0 = top.x;
640+
if (top.y > ny0)
641+
ny0 = top.y;
642+
if (top.x + top.w < nx1)
643+
nx1 = top.x + top.w;
644+
if (top.y + top.h < ny1)
645+
ny1 = top.y + top.h;
646+
}
647+
int nw = nx1 - nx0;
648+
int nh = ny1 - ny0;
649+
if (nw < 0)
650+
nw = 0;
651+
if (nh < 0)
652+
nh = 0;
653+
if (ct->clipdepth < CLIP_STACK_MAX) {
654+
ClipRect r = {nx0, ny0, nw, nh};
655+
ct->clipstack[ct->clipdepth++] = r;
656+
}
622657
ct->clipping = 1;
623-
ct->clipx = x0;
624-
ct->clipy = y0;
625-
ct->clipw = x1 - x0;
626-
ct->cliph = y1 - y0;
658+
ct->clipx = nx0;
659+
ct->clipy = ny0;
660+
ct->clipw = nw;
661+
ct->cliph = nh;
627662
break;
663+
}
628664
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END:
629-
ct->clipping = 0;
665+
if (ct->clipdepth > 0)
666+
ct->clipdepth--;
667+
if (ct->clipdepth > 0) {
668+
ClipRect top = ct->clipstack[ct->clipdepth - 1];
669+
ct->clipping = 1;
670+
ct->clipx = top.x;
671+
ct->clipy = top.y;
672+
ct->clipw = top.w;
673+
ct->cliph = top.h;
674+
} else {
675+
ct->clipping = 0;
676+
}
630677
break;
631678
default:
632679
break;

0 commit comments

Comments
 (0)