241 lines
7.7 KiB
C++
241 lines
7.7 KiB
C++
#pragma once
|
|
|
|
#include "foundation/result.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <span>
|
|
|
|
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<AppResizePlan> plan_app_resize(float width, float height)
|
|
{
|
|
if (!std::isfinite(width) || !std::isfinite(height)) {
|
|
return pp::foundation::Result<AppResizePlan>::failure(
|
|
pp::foundation::Status::invalid_argument("resize dimensions must be finite"));
|
|
}
|
|
|
|
if (width < 1.0F || height < 1.0F) {
|
|
return pp::foundation::Result<AppResizePlan>::failure(
|
|
pp::foundation::Status::invalid_argument("resize dimensions must be positive"));
|
|
}
|
|
|
|
if (width > static_cast<float>(std::numeric_limits<int>::max())
|
|
|| height > static_cast<float>(std::numeric_limits<int>::max())) {
|
|
return pp::foundation::Result<AppResizePlan>::failure(
|
|
pp::foundation::Status::out_of_range("resize dimensions exceed integer range"));
|
|
}
|
|
|
|
return pp::foundation::Result<AppResizePlan>::success(AppResizePlan {
|
|
.width = width,
|
|
.height = height,
|
|
.render_target_width = static_cast<int>(width),
|
|
.render_target_height = static_cast<int>(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<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
|