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; UI_Key key;
b32 is_showing; b32 is_showing;
f32 show; f32 show;
f32 scroll;
f32 scroll_begin;
V_TextboxState search_state; V_TextboxState search_state;
}; };

View File

@ -643,8 +643,9 @@ UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags)
b32 is_cursor_in_box = 0; b32 is_cursor_in_box = 0;
{ {
// TODO: More efficient test. This logic is just copied from the renderer's SDF function for now. // TODO: More efficient test. This logic is just copied from the renderer's SDF function for now.
Vec2 p0 = box->screen_rect.p0; Rng2 interactable_region = IntersectRng2(box->solved_scissor, box->screen_rect);
Vec2 p1 = box->screen_rect.p1; Vec2 p0 = interactable_region.p0;
Vec2 p1 = interactable_region.p1;
Vec2 point = frame->cursor_pos; Vec2 point = frame->cursor_pos;
b32 is_corner = 0; 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)); 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 // 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) if (!is_floating)
{ {
@ -1451,13 +1459,19 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
// Compute rect // Compute rect
Vec2 screen_pos = parent ? AddVec2(parent->screen_rect.p0, offset) : VEC2(0, 0); 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; Vec2 overshoot = Zi;
overshoot.x = MaxF32(0, (screen_pos.x + box->solved_dims.x) - parent->screen_rect.p1.x); if (!AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClampX))
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); overshoot.x = MaxF32(0, (screen_pos.x + box->solved_dims.x) - parent->screen_rect.p1.x);
screen_pos.y = MaxF32(parent->screen_rect.p0.y, screen_pos.y - overshoot.y); 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.p0 = screen_pos;
box->screen_rect.p1 = AddVec2(box->screen_rect.p0, box->solved_dims); 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 // Solve screen rounding
{ {
UI_Round rounding = box->desc.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 // 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 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)); 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 render data
// Build rect instance data // Build GPU rect data
for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index)
{ {
UI_Box *box = boxes_pre[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); GC_Run raw_run = UI_ScaleRun(frame->arena, raw_run_unscaled, box->solved_scale);
// TODO: Check that sprite image is not empty // TODO: Check that sprite image is not empty
b32 is_visible = 1; b32 should_upload = 1;
is_visible = is_visible && (box->desc.tint.w >= 0.0025); should_upload = should_upload && (box->desc.tint.w >= 0.0025);
is_visible = is_visible && (box->screen_rect.p1.x - box->screen_rect.p0.x > 0.0025); should_upload = should_upload && (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); should_upload = should_upload && (box->screen_rect.p1.y - box->screen_rect.p0.y > 0.0025);
is_visible = is_visible && ( should_upload = should_upload && (
!G_IsRefNil(box->raw_texture) || !G_IsRefNil(box->raw_texture) ||
!SPR_IsSheetKeyNil(box->desc.sprite_sheet) || !SPR_IsSheetKeyNil(box->desc.sprite_sheet) ||
(AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && raw_run.ready) || (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.background_color.a >= 0.0025 ||
box->desc.tint.a * box->desc.border_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); Vec4 tint_lin = LinearFromSrgb(box->desc.tint);
Vec2 box_dims = DimsFromRng2(box->screen_rect); Vec2 box_dims = DimsFromRng2(box->screen_rect);
UI_RegionPair child_alignment = UI_PairFromRegion(box->desc.child_alignment); 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); UI_GpuRect *rect = PushStruct(frame->rects_arena, UI_GpuRect);
rect->bounds = box->screen_rect; rect->bounds = box->screen_rect;
rect->scissor = box->solved_scissor;
rect->background_lin = LinearFromSrgb(box->desc.background_color); rect->background_lin = LinearFromSrgb(box->desc.background_color);
rect->border_lin = LinearFromSrgb(box->desc.border_color); rect->border_lin = LinearFromSrgb(box->desc.border_color);
rect->debug_lin = debug_lin; rect->debug_lin = debug_lin;
@ -1695,6 +1725,7 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
rect->bounds = rr.bounds; rect->bounds = rr.bounds;
rect->bounds = AddRng2Vec2(rect->bounds, baseline); rect->bounds = AddRng2Vec2(rect->bounds, baseline);
rect->bounds = AddRng2Vec2(rect->bounds, VEC2(rr.baseline_pos, 0)); 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_CaptureMouse = (1 << 1),
UI_BoxFlag_NoTextTruncation = (1 << 2), UI_BoxFlag_NoTextTruncation = (1 << 2),
UI_BoxFlag_Floating = (1 << 3), 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 //- Layout data
Vec2 solved_scale; Vec2 solved_scale;
Rng2 solved_scissor;
Vec2 final_children_size_accum; Vec2 final_children_size_accum;
Vec2 solved_dims; Vec2 solved_dims;
f32 cursor; 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 //- Compute border dist
f32 border_dist = 0; f32 border_dist = 0;
border_dist = abs(rect_dist); border_dist = abs(rect_dist);
@ -90,6 +87,9 @@ PixelShader(UI_DRectPS, UI_DRectPSOutput, UI_DRectPSInput input)
border_dist -= rect.border_size; border_dist -= rect.border_size;
} }
//- Compute borer color
Vec4 border_premul = input.base_border_premul;
//- Compute anti-aliased border-over-background color //- Compute anti-aliased border-over-background color
Vec4 composite_premul = 0; 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); composite_premul = lerp(background_premul, border_premul, border_coverage);
} }
//- Finalize
Vec4 result = composite_premul * input.tint_premul; 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) if (UI_GpuConst_DebugDraw)
{ {
result = input.debug_premul; result = input.debug_premul;

View File

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