-
-
Notifications
You must be signed in to change notification settings - Fork 612
Description
When an element X has the clip property set and contains elements that also have the clip property, X fails to clip its contents properly.
Minimal reproducible example and pictures:
With the Raylib renderer (modified from the `raylib-sidebar-scrolling-container` example):
Clay_BeginLayout();
CLAY({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0),
},
.padding = { 16, 16, 16, 16 },
.childGap = 16,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childAlignment = {
.y = CLAY_ALIGN_X_CENTER,
},
},
.backgroundColor = { 0, 0, 0, 255 },
}) {
CLAY({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_PERCENT(0.5),
},
.padding = { 16, 16, 16, 16 },
.childGap = 16,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.clip = {
.vertical = true,
.childOffset = Clay_GetScrollOffset(),
},
.backgroundColor = {50, 50, 50, 255}
}) {
for (size_t i = 0; i < 4; i++) {
CLAY({
.clip = {
.horizontal = true,
.childOffset = Clay_GetScrollOffset(),
},
.layout = {
.sizing = {
.height = CLAY_SIZING_PERCENT(0.4),
},
},
}) {
CLAY_TEXT(
CLAY_STRING("long text long text long text long text long text long text long text long text long text long text long text long text"),
CLAY_TEXT_CONFIG({
.fontId = 0,
.letterSpacing = 5,
.fontSize = 26,
.textColor = {255,255,255,255}
})
);
}
}
}
}
return Clay_EndLayout();
To the best of my knowledge, this behavior is a consequence of three issues:
- When the Raylib renderer encounters two
SCISSOR_STARTcommands without aSCISSOR_ENDin between, it overwrites the clip bounding box from the firstSCISSOR_STARTwith the one from the secondSCISSOR_START, and theSCISSOR_ENDcorresponding to the secondSCISSOR_STARTdisables all clipping. This causes elements rendered after the firstSCISSOR_ENDbut before the second one to not be clipped. (I fixed this by maintaining a stack of bounding boxes inside the renderer; aSCISSOR_STARTcauses the corresponding bounding box to be pushed onto the stack, and aSCISSOR_ENDpops an element off the stack and restores clipping corresponding to the bounding box that is now on the top of the stack.) - If a clip element A contains another clip element B, it is possible for the bounding box provided in the
SCISSOR_STARTcommand for element B to not be contained within the bounding box provided in theSCISSOR_STARTfor element A. (This caused problems for me because naively calling Raylib'sBeginScissorMode()with the bounding box provided by the command fails to account for the encompassing clip elements. To fix this, the bounding box of a newSCISSOR_STARTcommand should itself be clipped according to the bounding box of the top element of the stack.) - When a
clipelement is off screen, neither theSCISSOR_STARTnor theSCISSOR_ENDfor that element should be generated, but in reality theSCISSOR_ENDcommand alone is generated. This violates the invariant that aSCISSOR_ENDalways has an accompanyingSCISSOR_START(and puts the bounding box stack in an invalid state if it is implemented as described in (1)).
On a somewhat related note, the maximum number of scroll elements seems to be (somewhat arbitrarily) limited to 10.
I have my own fixes for all of these issues and I'm happy to make a PR, but I'd like to clarify some architectural questions first. Issue (3) is clearly a problem with the Clay layout algorithm itself, but I implemented fixes for (1) and (2) directly in the renderer. Is this the preferred separation of responsibilities? I can certainly see an argument for implementing the fixes directly in the layout algorithm, since the "correct" behavior seems pretty universally desirable.