Open LucasRo7 opened 9 months ago
We had to work around the same bug when we made the dash calendar, where we were re-ordering the week rows displayed in the calendar for the sliding animation. It was a rather frustrating one that was pretty difficult to diagnose.
Forgot this issue already existed, I'm adding the example from the ticket I've accidentally opened as duplicate to this one:
When having a nested mask on a UIX canvas, for example elements with masked parts inside of a masked scroll rect, updating the order offset of the individual elements leads to some of the elements being drawn back-to-front, where the background appears before the foreground.
I've created a minimal example object to demonstrate the issue. Folder link: resrec:///U-JackTheFoxOtter/R-33BE1DF44ECA7C605039BA30E716C23775C588DEF003CF8D579E556C18314443
It contains a simple masked scroll rect with 5 elements, each having a background, a text (foreground) and a mask. Note that the background is partially transparent so that it's possible to see if something is rendering behind it. The number displayed on the text is the current order offset of that element.
When clicking the "Flip" button to negate all order offsets, the last two elements in the newly sorted list get drawn back-to-front, where the background is rendered above the foreground. For non-transparent backgrounds, this makes it appear as if the content has disappeared completely.
If you disable or remove the mask from the template, then re-generate the list, flipping the order offset works as expected, and the the draw order of the elements is not affected.
The individual elements should always be drawn in the correct order regardless of their order offset.
Beta 2024.7.25.1284
I'm needing to dynamically re-order a list of complex elements (with nested masks) for a project I'm currently working on with xLinka and Tekno, this bug currently prevents us from re-ordering elements, which creates a big usability issue. This appears to be the same bug TheAutopilot and me ran into when working on the Dash Calendar for MMC23, so it has likely been around for a while.
You can see how without a transparent background, once the order is updated and the element runs into the reversed draw order issue, it appears as if the entire content has disappeared, when in fact it is just being rendered behind the background of the element.
GraphicsChunk
are ordered by the ReferenceID
of their RectTransform
. This is only equivalent to the order of UI elements for objects without reordered children.Slot
children does not invalidate the order of GraphicsChunk
children.ChunkRoots
with siblings.Those are two Harmony patches:
/// <summary>
/// This patch shows how ordering the children of a GraphicsChunk by their natural order in the slot hierarchy fixes the masking issues.
/// It is not designed to be efficient, but to show the principle.
/// </summary>
[HarmonyPatch(typeof(GraphicsChunk), "SortChildren")]
class GraphicsChunk_SortChildren_Patch
{//
[HarmonyPrefix]
static bool Prefix(GraphicsChunk __instance)
{
__instance.ChildrenChunks.Sort((GraphicsChunk a, GraphicsChunk b) =>
{
var commonParentA = a.Root.Slot;
var commonParentB = b.Root.Slot;
Slot commonChildA = commonParentA;
Slot commonChildB = commonParentB;
//1. Find common parent and the element's location within it.
while (commonParentA.Parent != null && !b.Root.Slot.IsChildOf(commonParentA))
{
commonChildA = commonParentA;
commonParentA = commonParentA.Parent;
}
while (commonParentB.Parent != null && !a.Root.Slot.IsChildOf(commonParentB))
{
commonChildB = commonParentB;
commonParentB = commonParentB.Parent;
}
//2. Check the relation between the two elements.
//Note: This order may not be valid if there is no common parent.
//This should never be the case as "this" should be among the parents.
// It also doesn't handle two nested GraphicsChunks. This also shouldn't happen, right?
int num = commonChildA.OrderOffset.CompareTo(commonChildB.OrderOffset);
if (num != 0 && commonParentA == commonParentB)
{
return num;
}
return commonChildA.ReferenceID.CompareTo(commonChildB.ReferenceID);
});
//false: replaces whole method
return false;
}
}
private static MethodInfo _Canvas_MarkGraphicChunksDirty = typeof(Canvas).GetMethod("MarkGraphicChunksDirty", AccessTools.all);
/// <summary>
/// This patch ensures that SortChildren is called by the Canvas when the order of children changes.
/// A proper marker should be implemented instead.
/// </summary>
[HarmonyPatch(typeof(RectTransform), "MarkChildrenOrderChanged")]
class RectTransform_MarkChildrenOrderChanged_Patch
{//
[HarmonyPostfix]
static void Postfix(RectTransform __instance, ref DataModelFlag ____dataModelFlags)
{
if (__instance.Canvas != null) {
_Canvas_MarkGraphicChunksDirty.Invoke(__instance.Canvas, null);
}
}
}
Seeking input from @ProbablePrime or @Frooxius on the above findings.
Defer to @Frooxius still not entirely familiar with how UIX rendering works.
Describe the bug?
If you have a UIX Mask under another image under a Horizontal Layout, if you change the order of the images under the horizontal layout, the Masked graphics start rendering behind the image
The problem is fixed if you create a copy of the UIX canvas(or save and reload the item) I couldnt find a way to "fix" this bug by disabling/enabling components, but putting the Slots in the original order does revert it back to how it looked before swapping
To Reproduce
Here are the steps to reproduce:
Create a UIX Canvas
Add a child with a Horizontal Layout component
Create 2 childs, each with an Image component
Under each child, add another child, with an image and a Mask component
If you've done everything correctly you should have something like this:
Change the order offset of the slots under the Horizontal Layout so that they are in a different order, one of the Masks and anything under it will start rendering under the Image that is parented directly under the Horizontal Layout
Alternatively, here's a link to the folder with a sample item:
resrec:///U-LucasRo7/R-9B973F98B21A2AF67CAF4F2B88E5E8A0C0B0EF351D2B0C4876B0EBA22311CC44
The sample item has a button that swaps the order of the slots under the Horizontal Layout, as described aboveExpected behavior
Be able to change the order of elements under the Horizontal Layout and still having masked child elements rendering over the parents.
Screenshots
This image shows what happens to the sample Item in the folder
resrec:///U-LucasRo7/R-9B973F98B21A2AF67CAF4F2B88E5E8A0C0B0EF351D2B0C4876B0EBA22311CC44
(Red arrows indicate pressing the button, or changing the order offset manually)
Resonite Version Number
2024.3.1.1178
What Platforms does this occur on?
Windows
What headset if any do you use?
Desktop/Quest 2
Log Files
Not applicable, but here it is anyway: DESKTOP-CPP5K63 - 2024.3.1.1178 - 2024-03-09 18_53_22.log
Additional Context
This issue also happens with a Vertical layout. Changing the order offset in a way that doesnt change the order of the children does not cause the issue Toggling "Show Mask Visual" on the Mask component does not alter the results(sample item has it on for demonstration purposes)
Reporters
LucasRo7 (Discord: lucasro7)