Plan UI observer frame clipping

This commit is contained in:
2026-06-05 07:18:48 +02:00
parent 942c053c19
commit a104f88360
8 changed files with 407 additions and 57 deletions

View File

@@ -495,54 +495,67 @@ void App::async_swap()
bool App::update_ui_observer(Node *n)
{
if (n && n->m_display)
{
auto box = n->m_clip_uncut;
Node* p = n->m_parent;
while (p)
{
float pt = YGNodeLayoutGetPadding(p->y_node, YGEdgeTop);
float pr = YGNodeLayoutGetPadding(p->y_node, YGEdgeRight);
float pb = YGNodeLayoutGetPadding(p->y_node, YGEdgeBottom);
float pl = YGNodeLayoutGetPadding(p->y_node, YGEdgeLeft);
glm::vec2 off_p(pl, pt);
glm::vec2 off_s(pr, pb);
//glm::vec2 parent_offset = p->m_parent ? -p->m_parent->m_pos_offset_childred : glm::vec2(0.f);
glm::vec4 pclip = { xy(p->m_clip_uncut) + off_p, zw(p->m_clip_uncut) - off_s - off_p/* + parent_offset*/ };
box = rect_intersection(box, pclip);
p = p->m_parent;
std::vector<pp::app::AppUiObserverParentClip> parent_clips;
if (n) {
for (Node* p = n->m_parent; p; p = p->m_parent) {
parent_clips.push_back(pp::app::AppUiObserverParentClip {
.clip = pp::app::AppUiObserverRect {
.x = p->m_clip_uncut.x,
.y = p->m_clip_uncut.y,
.width = p->m_clip_uncut.z,
.height = p->m_clip_uncut.w,
},
.padding_top = YGNodeLayoutGetPadding(p->y_node, YGEdgeTop),
.padding_right = YGNodeLayoutGetPadding(p->y_node, YGEdgeRight),
.padding_bottom = YGNodeLayoutGetPadding(p->y_node, YGEdgeBottom),
.padding_left = YGNodeLayoutGetPadding(p->y_node, YGEdgeLeft),
});
}
//auto box = n->m_clip;
//glm::ivec4 c = glm::vec4((int)box.x - 1, (int)(height / zoom - box.y - box.w) - 1, (int)box.z + 2, (int)box.w + 2) * zoom;
glm::vec2 parent_offset = n->m_parent ? n->m_parent->m_pos_offset_childred : glm::vec2(0.f);
if (box.z <= 0 || box.w <= 0)
{
if (n->m_on_screen)
{
if (dynamic_cast<NodeStrokePreview*>(n))
p = p;
n->handle_on_screen(true, false);
n->m_on_screen = false;
}
return false;
}
if (!n->m_on_screen)
{
n->handle_on_screen(false, true);
n->m_on_screen = true;
}
glm::ivec4 c = glm::vec4(box.x - 1, (height / zoom - box.y - box.w - 1), box.z + 2, box.w + 2) * zoom;
apply_app_scissor(pp::renderer::gl::OpenGlScissorRect {
.enabled = 1U,
.x = static_cast<std::int32_t>(floorf(c.x + off_x)),
.y = static_cast<std::int32_t>(floorf(c.y + off_y)),
.width = static_cast<std::int32_t>(ceilf(c.z)),
.height = static_cast<std::int32_t>(ceilf(c.w)),
});
n->draw();
return true;
}
return false;
const auto plan = pp::app::plan_app_ui_observer(
n != nullptr,
n && n->m_display,
n && n->m_on_screen,
n
? pp::app::AppUiObserverRect {
.x = n->m_clip_uncut.x,
.y = n->m_clip_uncut.y,
.width = n->m_clip_uncut.z,
.height = n->m_clip_uncut.w,
}
: pp::app::AppUiObserverRect {},
parent_clips,
height,
zoom,
off_x,
off_y);
if (!plan) {
LOG("UI observer plan failed: %s", plan.status().message);
return false;
}
if (!n)
return false;
if (plan.value().notify_leave_screen)
n->handle_on_screen(true, false);
if (plan.value().notify_enter_screen)
n->handle_on_screen(false, true);
n->m_on_screen = plan.value().next_on_screen;
if (!plan.value().draw_node)
return false;
apply_app_scissor(pp::renderer::gl::OpenGlScissorRect {
.enabled = 1U,
.x = plan.value().scissor_x,
.y = plan.value().scissor_y,
.width = plan.value().scissor_width,
.height = plan.value().scissor_height,
});
n->draw();
return true;
}
void App::draw(float dt)

View File

@@ -3,7 +3,9 @@
#include "foundation/result.h"
#include <cmath>
#include <cstdint>
#include <limits>
#include <span>
namespace pp::app {
@@ -39,6 +41,33 @@ struct AppResizePlan {
bool request_redraw = true;
};
struct AppUiObserverRect {
float x = 0.0F;
float y = 0.0F;
float width = 0.0F;
float height = 0.0F;
};
struct AppUiObserverParentClip {
AppUiObserverRect clip;
float padding_top = 0.0F;
float padding_right = 0.0F;
float padding_bottom = 0.0F;
float padding_left = 0.0F;
};
struct AppUiObserverPlan {
bool draw_node = false;
bool notify_enter_screen = false;
bool notify_leave_screen = false;
bool next_on_screen = false;
AppUiObserverRect visible_clip;
std::int32_t scissor_x = 0;
std::int32_t scissor_y = 0;
std::int32_t scissor_width = 0;
std::int32_t scissor_height = 0;
};
[[nodiscard]] constexpr AppInitialSurfacePlan plan_app_initial_surface() noexcept
{
return AppInitialSurfacePlan {
@@ -110,4 +139,102 @@ struct AppResizePlan {
});
}
[[nodiscard]] constexpr AppUiObserverRect intersect_app_ui_observer_rect(
AppUiObserverRect a,
AppUiObserverRect b) noexcept
{
const float x0 = a.x > b.x ? a.x : b.x;
const float y0 = a.y > b.y ? a.y : b.y;
const float x1 = (a.x + a.width) < (b.x + b.width) ? (a.x + a.width) : (b.x + b.width);
const float y1 = (a.y + a.height) < (b.y + b.height) ? (a.y + a.height) : (b.y + b.height);
return AppUiObserverRect {
.x = x0,
.y = y0,
.width = x1 - x0,
.height = y1 - y0,
};
}
[[nodiscard]] inline pp::foundation::Result<AppUiObserverPlan> plan_app_ui_observer(
bool has_node,
bool display,
bool was_on_screen,
AppUiObserverRect node_clip,
std::span<const AppUiObserverParentClip> parent_clips,
float surface_height,
float zoom,
float offset_x,
float offset_y)
{
if (!has_node || !display) {
return pp::foundation::Result<AppUiObserverPlan>::success(AppUiObserverPlan {
.draw_node = false,
.next_on_screen = was_on_screen,
.visible_clip = node_clip,
});
}
const auto finite_rect = [](AppUiObserverRect rect) noexcept {
return std::isfinite(rect.x) && std::isfinite(rect.y)
&& std::isfinite(rect.width) && std::isfinite(rect.height);
};
if (!finite_rect(node_clip) || !std::isfinite(surface_height)
|| !std::isfinite(zoom) || !std::isfinite(offset_x) || !std::isfinite(offset_y)) {
return pp::foundation::Result<AppUiObserverPlan>::failure(
pp::foundation::Status::invalid_argument("UI observer geometry must be finite"));
}
if (surface_height < 1.0F || zoom <= 0.0F) {
return pp::foundation::Result<AppUiObserverPlan>::failure(
pp::foundation::Status::invalid_argument("UI observer surface height and zoom must be positive"));
}
AppUiObserverRect visible = node_clip;
for (const auto& parent : parent_clips) {
if (!finite_rect(parent.clip)
|| !std::isfinite(parent.padding_top)
|| !std::isfinite(parent.padding_right)
|| !std::isfinite(parent.padding_bottom)
|| !std::isfinite(parent.padding_left)) {
return pp::foundation::Result<AppUiObserverPlan>::failure(
pp::foundation::Status::invalid_argument("UI observer parent geometry must be finite"));
}
const AppUiObserverRect padded {
.x = parent.clip.x + parent.padding_left,
.y = parent.clip.y + parent.padding_top,
.width = parent.clip.width - parent.padding_right - parent.padding_left,
.height = parent.clip.height - parent.padding_bottom - parent.padding_top,
};
visible = intersect_app_ui_observer_rect(visible, padded);
}
if (visible.width <= 0.0F || visible.height <= 0.0F) {
return pp::foundation::Result<AppUiObserverPlan>::success(AppUiObserverPlan {
.draw_node = false,
.notify_leave_screen = was_on_screen,
.next_on_screen = false,
.visible_clip = visible,
});
}
const float projected_x = (visible.x - 1.0F) * zoom;
const float projected_y = (surface_height / zoom - visible.y - visible.height - 1.0F) * zoom;
const float projected_width = (visible.width + 2.0F) * zoom;
const float projected_height = (visible.height + 2.0F) * zoom;
return pp::foundation::Result<AppUiObserverPlan>::success(AppUiObserverPlan {
.draw_node = true,
.notify_enter_screen = !was_on_screen,
.notify_leave_screen = false,
.next_on_screen = true,
.visible_clip = visible,
.scissor_x = static_cast<std::int32_t>(std::floor(projected_x + offset_x)),
.scissor_y = static_cast<std::int32_t>(std::floor(projected_y + offset_y)),
.scissor_width = static_cast<std::int32_t>(std::ceil(projected_width)),
.scissor_height = static_cast<std::int32_t>(std::ceil(projected_height)),
});
}
} // namespace pp::app