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

View File

@ -2519,7 +2519,7 @@ void V_TickForever(WaveLaneCtx *lane)
frame->shade_cursor = MulAffineVec2(frame->af.screen_to_shade, frame->screen_cursor);
frame->world_cursor = MulAffineVec2(frame->af.screen_to_world, frame->screen_cursor);
b32 show_editor_ui = TweakBool("Show editor UI", 1);
b32 show_editor_ui = TweakBool("Show editor UI", 0);
frame->world_selection_start = frame->world_cursor;
if (frame->is_editing)
@ -4370,13 +4370,29 @@ void V_TickForever(WaveLaneCtx *lane)
V_Palette *palette = &frame->palette;
{
UI_Push(Tag, HashF("developer command palette"));
UI_Size total_width = UI_FNT(40, 1);
UI_Size total_height = UI_FNT(40, 1);
UI_Size header_height = UI_FNT(1.25, 1);
UI_Size header_height = UI_FNT(1.3, 1);
UI_Size window_padding = UI_FNT(0.5, 1);
UI_Size col0_width = UI_FNT(1.75, 1);
UI_Size col1_width = UI_FNT(30, 1);
UI_Size col2_width = UI_FNT(10, 1);
UI_Size scrollbar_width = UI_FNT(1, 1);
UI_Key lister_key = UI_KeyF("lister");
UI_Key scrollbar_key = UI_KeyF("scrollbar");
UI_Key thumb_key = UI_KeyF("scrollbar-thumb");
b32 scrollbar_visible = 1;
UI_BoxReports scrollbar_reps = UI_ReportsFromKey(scrollbar_key);
UI_BoxReports thumb_reps = UI_ReportsFromKey(thumb_key);
f32 lister_offset = palette->scroll;
{
f32 ease_rate = TweakFloat("Debug palette ease rate", 20, 1, 100) * frame->dt;
@ -4428,7 +4444,7 @@ void V_TickForever(WaveLaneCtx *lane)
UI_PushCP(UI_BuildBoxEx(palette->key));
{
//////////////////////////////
//- Build header
//- Build title bar
UI_PushCP(UI_NilKey);
{
@ -4464,6 +4480,7 @@ void V_TickForever(WaveLaneCtx *lane)
}
UI_PopCP(UI_TopCP());
//- Window box
UI_SetNext(BackgroundColor, 0);
UI_SetNext(Rounding, 0);
UI_SetNext(Width, UI_GROW(1, 0));
@ -4698,9 +4715,6 @@ void V_TickForever(WaveLaneCtx *lane)
UI_BuildBoxEx(caret_box);
}
}
}
UI_PopCP(UI_TopCP());
}
@ -4708,12 +4722,32 @@ void V_TickForever(WaveLaneCtx *lane)
UI_PopCP(UI_TopCP());
}
UI_BuildDivider(UI_PIX(1, 1), divider_color, Axis_Y);
//////////////////////////////
//- Build palette items list
// Scissor box
UI_SetNext(BackgroundColor, 0);
UI_SetNext(Rounding, 0);
UI_SetNext(Width, UI_GROW(1, 0));
UI_SetNext(Height, UI_GROW(1, 0));
UI_SetNext(Flags, UI_BoxFlag_Scissor);
UI_PushCP(UI_BuildRow());
{
// Items & Lister group
UI_SetNext(Tint, 0);
UI_SetNext(Rounding, 0);
UI_PushCP(UI_BuildRow());
{
// Items box
UI_SetNext(BackgroundColor, 0);
UI_SetNext(Rounding, 0);
UI_SetNext(Width, UI_GROW(1, 0));
UI_SetNext(Height, UI_GROW(1, 0));
UI_SetNext(FloatingPos, VEC2(0, -lister_offset));
UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampY);
UI_PushCP(UI_BuildRowEx(lister_key));
{
UI_SetNext(Tint, 0);
UI_SetNext(Rounding, 0);
@ -4793,7 +4827,10 @@ void V_TickForever(WaveLaneCtx *lane)
for (PaletteItem *item = first_item; item; item = item->next)
{
// Divider
if (item != first_item)
{
UI_BuildDivider(UI_PIX(1, 1), divider_color, Axis_Y);
}
UI_BoxReport item_rep = UI_ReportsFromKey(item->key).draw;
if (item_rep.m1.presses)
@ -5054,7 +5091,7 @@ void V_TickForever(WaveLaneCtx *lane)
// UI_SetNext(Anchor, UI_Region_Center);
// UI_SetNext(FloatingPos, VEC2(marker_pos, (marker_size_px * 0.5)));
UI_SetNext(FloatingPos, VEC2(marker_pos, -marker_dims.y * 0.125));
UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClamp | UI_BoxFlag_CaptureMouse);
UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampX | UI_BoxFlag_NoFloatingClampY | UI_BoxFlag_CaptureMouse);
UI_BuildBoxEx(marker_key);
}
}
@ -5130,17 +5167,167 @@ void V_TickForever(WaveLaneCtx *lane)
}
UI_PopCP(UI_TopCP());
if (scrollbar_visible)
{
Vec2 scrollbar_pos = scrollbar_reps.draw.screen_rect.p0;
Vec2 scrollbar_dims = DimsFromRng2(scrollbar_reps.draw.screen_rect);
Vec2 thumb_dims = DimsFromRng2(thumb_reps.draw.screen_rect);
// Vec2 half_thumb_dims = MulVec2(thumb_dims, 0.5);
// f32 range_min = tweak_var.range.min;
// f32 range_max = tweak_var.range.max;
// if (range_max <= range_min)
// {
// range_max = range_min + 1;
// }
// f32 ratio = 0;
// ratio = (tweak_float - range_min) / (range_max - range_min);
// ratio = ClampF32(ratio, 0, 1);
if (thumb_reps.draw.m1.downs)
{
palette->scroll_begin = palette->scroll;
}
if (thumb_reps.draw.m1.held)
{
palette->scroll = palette->scroll_begin + (frame->screen_cursor.y - ui_frame->drag_cursor_pos.y);
}
palette->scroll = MaxF32(palette->scroll, 0);
// f32 thumb_offset = 50;
f32 thumb_offset = palette->scroll;
UI_SetNext(Width, scrollbar_width);
UI_PushCP(UI_BuildBoxEx(scrollbar_key));
{
UI_Size thumb_height = UI_FNT(10, 1);
Vec4 thumb_color = VEC4(0, 0.5, 1, 1);
thumb_color.a = thumb_reps.draw.hot * 0.5 + 0.5;
UI_SetNext(BackgroundColor, thumb_color);
UI_SetNext(Width, UI_GROW(1, 0));
UI_SetNext(Height, thumb_height);
UI_SetNext(Rounding, UI_RGROW(1 * theme.rounding));
UI_SetNext(FloatingPos, VEC2(0, thumb_offset));
// UI_SetNext(Anchor, UI_Region_Center);
UI_SetNext(Anchor, UI_Region_Top);
UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_Floating);
UI_SetNext(Anchor, UI_Region_Top);
UI_BuildBoxEx(thumb_key);
}
UI_PopCP(UI_TopCP());
}
}
UI_PopCP(UI_TopCP());
}
UI_PopCP(UI_TopCP());
UI_BuildSpacer(window_padding, Axis_X);
// UI_BuildSpacer(UI_FNT(100, 1), Axis_Y);
// FIXME: Remove this
// if (scrollbar_visible)
// {
// Vec2 scrollbar_pos = scrollbar_reps.draw.screen_rect.p0;
// Vec2 scrollbar_dims = DimsFromRng2(scrollbar_reps.draw.screen_rect);
// Vec2 thumb_dims = DimsFromRng2(thumb_reps.draw.screen_rect);
// // Vec2 half_thumb_dims = MulVec2(thumb_dims, 0.5);
// // f32 range_min = tweak_var.range.min;
// // f32 range_max = tweak_var.range.max;
// // if (range_max <= range_min)
// // {
// // range_max = range_min + 1;
// // }
// // f32 ratio = 0;
// // ratio = (tweak_float - range_min) / (range_max - range_min);
// // ratio = ClampF32(ratio, 0, 1);
// if (thumb_reps.draw.m1.downs)
// {
// palette->scroll_begin = palette->scroll;
// }
// if (thumb_reps.draw.m1.held)
// {
// palette->scroll = palette->scroll_begin + (frame->screen_cursor.y - ui_frame->drag_cursor_pos.y);
// }
// palette->scroll = MaxF32(palette->scroll, 0);
// // f32 thumb_offset = 50;
// f32 thumb_offset = palette->scroll;
// UI_SetNext(Width, scrollbar_width);
// UI_PushCP(UI_BuildBoxEx(scrollbar_key));
// {
// UI_Size thumb_height = UI_FNT(10, 1);
// Vec4 thumb_color = VEC4(0, 0.5, 1, 1);
// thumb_color.a = thumb_reps.draw.hot * 0.5 + 0.5;
// UI_SetNext(BackgroundColor, thumb_color);
// UI_SetNext(Width, UI_GROW(1, 0));
// UI_SetNext(Height, thumb_height);
// UI_SetNext(Rounding, UI_RGROW(1 * theme.rounding));
// UI_SetNext(FloatingPos, VEC2(0, thumb_offset));
// // UI_SetNext(Anchor, UI_Region_Center);
// UI_SetNext(Anchor, UI_Region_Top);
// UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_Floating);
// UI_SetNext(Anchor, UI_Region_Top);
// UI_BuildBoxEx(thumb_key);
// }
// UI_PopCP(UI_TopCP());
// }
}
UI_PopCP(UI_TopCP());
// UI_BuildSpacer(window_padding, Axis_Y);
UI_BuildDivider(UI_PIX(1, 1), divider_color, Axis_Y);
UI_BuildSpacer(header_height, Axis_Y);
}
UI_PopCP(UI_TopCP());
}
// UI_PopCP(UI_TopCP());
UI_PopCP(palette_cp);
}
UI_Pop(Tag);
}
@ -6314,7 +6501,7 @@ void V_TickForever(WaveLaneCtx *lane)
UI_SetNext(Anchor, UI_Region_Center);
UI_SetNext(FloatingPos, timeline_pos);
UI_SetNext(BackgroundColor, tmld_bg);
UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClamp);
UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampX | UI_BoxFlag_NoFloatingClampY);
UI_PushCP(UI_BuildRowEx(timeline_key));
{
}
@ -6374,7 +6561,7 @@ void V_TickForever(WaveLaneCtx *lane)
tint.a *= marker_opacity;
UI_SetNext(Tint, tint);
UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClamp);
UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampX | UI_BoxFlag_NoFloatingClampY);
UI_SetNext(BackgroundColor, marker_color);
UI_SetNext(Anchor, UI_Region_Center);
UI_SetNext(Width, width);

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,10 +1311,17 @@ 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)
{
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)
{
size_accum += new_size;
@ -1451,14 +1459,20 @@ 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;
if (!AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClampX))
{
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);
}
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);
box->screen_anchor = AddVec2(box->screen_rect.p0, anchor_offset);
@ -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;