#pragma once #include "foundation/result.h" #include #include #include #include namespace pp::app { struct AppInitialSurfacePlan { float width = 960.0F; float height = 540.0F; }; struct AppFrameUpdatePlan { bool update_frame = false; bool update_layouts = false; bool refresh_canvas_toolbar = false; }; struct AppFrameDrawPlan { bool draw_canvas_stroke = false; bool draw_vr_ui = false; bool draw_main_ui = true; bool reset_redraw = true; }; struct AppFrameTickPlan { bool tick_designer_layout = false; bool tick_main_layout = false; }; struct AppResizePlan { float width = 0.0F; float height = 0.0F; int render_target_width = 0; int render_target_height = 0; bool recreate_ui_render_target = true; 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 { .width = 1920.0F / 2.0F, .height = 1080.0F / 2.0F, }; } [[nodiscard]] constexpr AppFrameUpdatePlan plan_app_frame_update(bool redraw, bool animate) noexcept { const bool update_frame = redraw || animate; return AppFrameUpdatePlan { .update_frame = update_frame, .update_layouts = update_frame, .refresh_canvas_toolbar = update_frame, }; } [[nodiscard]] constexpr AppFrameDrawPlan plan_app_frame_draw( bool has_canvas_node, bool has_canvas_document, bool vr_active, bool ui_visible, bool vr_only) noexcept { return AppFrameDrawPlan { .draw_canvas_stroke = has_canvas_node && has_canvas_document, .draw_vr_ui = vr_active && ui_visible, .draw_main_ui = !vr_only, .reset_redraw = true, }; } [[nodiscard]] constexpr AppFrameTickPlan plan_app_frame_tick( bool has_designer_layout, bool has_main_layout) noexcept { return AppFrameTickPlan { .tick_designer_layout = has_designer_layout, .tick_main_layout = has_main_layout, }; } [[nodiscard]] inline pp::foundation::Result plan_app_resize(float width, float height) { if (!std::isfinite(width) || !std::isfinite(height)) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("resize dimensions must be finite")); } if (width < 1.0F || height < 1.0F) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("resize dimensions must be positive")); } if (width > static_cast(std::numeric_limits::max()) || height > static_cast(std::numeric_limits::max())) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("resize dimensions exceed integer range")); } return pp::foundation::Result::success(AppResizePlan { .width = width, .height = height, .render_target_width = static_cast(width), .render_target_height = static_cast(height), .recreate_ui_render_target = true, .request_redraw = true, }); } [[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 plan_app_ui_observer( bool has_node, bool display, bool was_on_screen, AppUiObserverRect node_clip, std::span parent_clips, float surface_height, float zoom, float offset_x, float offset_y) { if (!has_node || !display) { return pp::foundation::Result::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::failure( pp::foundation::Status::invalid_argument("UI observer geometry must be finite")); } if (surface_height < 1.0F || zoom <= 0.0F) { return pp::foundation::Result::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::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::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::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::floor(projected_x + offset_x)), .scissor_y = static_cast(std::floor(projected_y + offset_y)), .scissor_width = static_cast(std::ceil(projected_width)), .scissor_height = static_cast(std::ceil(projected_height)), }); } } // namespace pp::app