UI scissoring

This commit is contained in:
jacob 2026-03-29 20:33:41 -05:00
parent c530ec16c0
commit 5c2ff7ca9f
6 changed files with 624 additions and 392 deletions

File diff suppressed because it is too large Load Diff

View File

@ -212,6 +212,8 @@ Struct(V_Palette)
UI_Key key;
b32 is_showing;
f32 show;
f32 scroll;
f32 scroll_begin;
V_TextboxState search_state;
};

View File

@ -643,8 +643,9 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags)
b32 is_cursor_in_box = 0;
{
// TODO: More efficient test. This logic is just copied from the renderer's SDF function for now.
Vec2 p0 = box->screen_rect.p0;
Vec2 p1 = box->screen_rect.p1;
Rng2 interactable_region = IntersectRng2(box->solved_scissor, box->screen_rect);
Vec2 p0 = interactable_region.p0;
Vec2 p1 = interactable_region.p1;
Vec2 point = frame->cursor_pos;
b32 is_corner = 0;
f32 non_corner_edge_dist = MinF32(MinF32(point.x - p0.x, p1.x - point.x), MinF32(point.y - p0.y, p1.y - point.y));
@ -1310,9 +1311,16 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
}
}
// Solve floating violation
if (is_floating && new_size > box_size && !AnyBit(child->desc.flags, UI_BoxFlag_NoFloatingClamp))
if (is_floating && new_size > box_size)
{
new_size = MaxF32(new_size - flex, box_size);
b32 should_clamp = (
!(axis == Axis_X && AnyBit(child->desc.flags, UI_BoxFlag_NoFloatingClampX)) &&
!(axis == Axis_Y && AnyBit(child->desc.flags, UI_BoxFlag_NoFloatingClampY))
);
if (should_clamp)
{
new_size = MaxF32(new_size - flex, box_size);
}
}
if (!is_floating)
{
@ -1451,13 +1459,19 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
// Compute rect
Vec2 screen_pos = parent ? AddVec2(parent->screen_rect.p0, offset) : VEC2(0, 0);
if (is_floating && !AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClamp))
if (is_floating)
{
Vec2 overshoot = Zi;
overshoot.x = MaxF32(0, (screen_pos.x + box->solved_dims.x) - parent->screen_rect.p1.x);
overshoot.y = MaxF32((screen_pos.y + box->solved_dims.y) - parent->screen_rect.p1.y, 0);
screen_pos.x = MaxF32(parent->screen_rect.p0.x, screen_pos.x - overshoot.x);
screen_pos.y = MaxF32(parent->screen_rect.p0.y, screen_pos.y - overshoot.y);
if (!AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClampX))
{
overshoot.x = MaxF32(0, (screen_pos.x + box->solved_dims.x) - parent->screen_rect.p1.x);
screen_pos.x = MaxF32(parent->screen_rect.p0.x, screen_pos.x - overshoot.x);
}
if (!AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClampY))
{
overshoot.y = MaxF32((screen_pos.y + box->solved_dims.y) - parent->screen_rect.p1.y, 0);
screen_pos.y = MaxF32(parent->screen_rect.p0.y, screen_pos.y - overshoot.y);
}
}
box->screen_rect.p0 = screen_pos;
box->screen_rect.p1 = AddVec2(box->screen_rect.p0, box->solved_dims);
@ -1470,6 +1484,19 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
}
}
// Solve scissor
{
box->solved_scissor = Rng2Inf;
if (box->desc.flags & UI_BoxFlag_Scissor)
{
box->solved_scissor = box->screen_rect;
}
if (parent)
{
box->solved_scissor = IntersectRng2(box->solved_scissor, parent->solved_scissor);
}
}
// Solve screen rounding
{
UI_Round rounding = box->desc.rounding;
@ -1500,7 +1527,8 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
}
// Clamp rounding based on parent rounding
if (parent && !AllBits(box->desc.flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClamp))
// if (parent && !AllBits(box->desc.flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClamp))
if (parent)
{
Vec2 vtl = SubVec2(VEC2(parent->screen_rect.p0.x, parent->screen_rect.p0.y), VEC2(box->screen_rect.p0.x, box->screen_rect.p0.y));
Vec2 vtr = SubVec2(VEC2(parent->screen_rect.p1.x, parent->screen_rect.p0.y), VEC2(box->screen_rect.p1.x, box->screen_rect.p0.y));
@ -1529,7 +1557,7 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
//////////////////////////////
//- Build render data
// Build rect instance data
// Build GPU rect data
for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index)
{
UI_Box *box = boxes_pre[pre_index];
@ -1537,21 +1565,22 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
GC_Run raw_run = UI_ScaleRun(frame->arena, raw_run_unscaled, box->solved_scale);
// TODO: Check that sprite image is not empty
b32 is_visible = 1;
is_visible = is_visible && (box->desc.tint.w >= 0.0025);
is_visible = is_visible && (box->screen_rect.p1.x - box->screen_rect.p0.x > 0.0025);
is_visible = is_visible && (box->screen_rect.p1.y - box->screen_rect.p0.y > 0.0025);
is_visible = is_visible && (
b32 should_upload = 1;
should_upload = should_upload && (box->desc.tint.w >= 0.0025);
should_upload = should_upload && (box->screen_rect.p1.x - box->screen_rect.p0.x > 0.0025);
should_upload = should_upload && (box->screen_rect.p1.y - box->screen_rect.p0.y > 0.0025);
should_upload = should_upload && (
!G_IsRefNil(box->raw_texture) ||
!SPR_IsSheetKeyNil(box->desc.sprite_sheet) ||
(AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && raw_run.ready) ||
box->desc.tint.a * box->desc.background_color.a >= 0.0025 ||
box->desc.tint.a * box->desc.border_color.a >= 0.0025
);
should_upload = should_upload && (!IsRng2Empty(IntersectRng2(box->screen_rect, box->solved_scissor)));
if (is_visible || AnyBit(frame->frame_flags, UI_FrameFlag_Debug))
if (should_upload || AnyBit(frame->frame_flags, UI_FrameFlag_Debug))
{
Vec4 debug_lin = is_visible ? LinearFromSrgb(box->desc.debug_color) : LinearFromSrgb(box->desc.invisible_debug_color);
Vec4 debug_lin = should_upload ? LinearFromSrgb(box->desc.debug_color) : LinearFromSrgb(box->desc.invisible_debug_color);
Vec4 tint_lin = LinearFromSrgb(box->desc.tint);
Vec2 box_dims = DimsFromRng2(box->screen_rect);
UI_RegionPair child_alignment = UI_PairFromRegion(box->desc.child_alignment);
@ -1562,6 +1591,7 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
{
UI_GpuRect *rect = PushStruct(frame->rects_arena, UI_GpuRect);
rect->bounds = box->screen_rect;
rect->scissor = box->solved_scissor;
rect->background_lin = LinearFromSrgb(box->desc.background_color);
rect->border_lin = LinearFromSrgb(box->desc.border_color);
rect->debug_lin = debug_lin;
@ -1695,6 +1725,7 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
rect->bounds = rr.bounds;
rect->bounds = AddRng2Vec2(rect->bounds, baseline);
rect->bounds = AddRng2Vec2(rect->bounds, VEC2(rr.baseline_pos, 0));
rect->scissor = box->solved_scissor;
}
}
}

View File

@ -90,7 +90,9 @@ Enum(UI_BoxFlag)
UI_BoxFlag_CaptureMouse = (1 << 1),
UI_BoxFlag_NoTextTruncation = (1 << 2),
UI_BoxFlag_Floating = (1 << 3),
UI_BoxFlag_NoFloatingClamp = (1 << 4),
UI_BoxFlag_NoFloatingClampX = (1 << 4),
UI_BoxFlag_NoFloatingClampY = (1 << 5),
UI_BoxFlag_Scissor = (1 << 6),
};
////////////////////////////////////////////////////////////
@ -322,6 +324,7 @@ Struct(UI_Box)
//- Layout data
Vec2 solved_scale;
Rng2 solved_scissor;
Vec2 final_children_size_accum;
Vec2 solved_dims;
f32 cursor;

View File

@ -79,9 +79,6 @@ PixelShader(UI_DRectPS, UI_DRectPSOutput, UI_DRectPSInput input)
}
}
//- Compute borer color
Vec4 border_premul = input.base_border_premul;
//- Compute border dist
f32 border_dist = 0;
border_dist = abs(rect_dist);
@ -90,6 +87,9 @@ PixelShader(UI_DRectPS, UI_DRectPSOutput, UI_DRectPSInput input)
border_dist -= rect.border_size;
}
//- Compute borer color
Vec4 border_premul = input.base_border_premul;
//- Compute anti-aliased border-over-background color
Vec4 composite_premul = 0;
{
@ -98,8 +98,16 @@ PixelShader(UI_DRectPS, UI_DRectPSOutput, UI_DRectPSInput input)
composite_premul = lerp(background_premul, border_premul, border_coverage);
}
//- Finalize
Vec4 result = composite_premul * input.tint_premul;
//- Scissor
// FIXME: Inclusive
if (p.x < rect.scissor.p0.x || p.x > rect.scissor.p1.x || p.y < rect.scissor.p0.y || p.y > rect.scissor.p1.y)
{
result = 0;
}
//- Finalize
if (UI_GpuConst_DebugDraw)
{
result = input.debug_premul;

View File

@ -10,6 +10,7 @@ G_DeclConstant(b32, UI_GpuConst_DebugDraw, 1);
Struct(UI_GpuRect)
{
Rng2 bounds;
Rng2 scissor;
G_TextureRef tex;
Rng2 tex_slice_uv;