Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
hit_test.hpp
Go to the documentation of this file.
1#pragma once
2
5
7
8#include <algorithm>
9#include <cmath>
10#include <cstdint>
11#include <limits>
12#include <optional>
13#include <span>
14
15namespace pulp::timeline_editor {
16
20
28 float left_px = 0.0f;
29 float top_px = 0.0f;
30 float right_px = 0.0f;
31 float bottom_px = 0.0f;
32
33 constexpr bool operator==(const ProjectedHitCandidate&) const = default;
34};
35
37enum class ProjectedHitRegion : std::uint8_t {
41};
42
50
51namespace hit_test_detail {
52
53inline bool valid_candidate(const ProjectedHitCandidate& candidate) noexcept {
54 return candidate.item.valid() && std::isfinite(candidate.left_px) &&
55 std::isfinite(candidate.top_px) && std::isfinite(candidate.right_px) &&
56 std::isfinite(candidate.bottom_px) && candidate.left_px < candidate.right_px &&
57 candidate.top_px < candidate.bottom_px;
58}
59
60inline bool inside_expanded_half_open(float value, float start, float end,
61 float tolerance) noexcept {
62 const auto wide_value = static_cast<double>(value);
63 const auto wide_tolerance = static_cast<double>(tolerance);
64 return wide_value >= static_cast<double>(start) - wide_tolerance &&
65 wide_value < static_cast<double>(end) + wide_tolerance;
66}
67
68inline bool inside_half_open(const ProjectedHitCandidate& candidate, float x, float y) noexcept {
69 return x >= candidate.left_px && x < candidate.right_px && y >= candidate.top_px &&
70 y < candidate.bottom_px;
71}
72
73inline double distance_to_half_open_span(float value, float start, float end) noexcept {
74 if (value < start)
75 return static_cast<double>(start) - static_cast<double>(value);
76 if (value >= end)
77 return static_cast<double>(value) - static_cast<double>(end);
78 return 0.0;
79}
80
81inline ProjectedHitRegion region_at(const ProjectedHitCandidate& candidate, float x,
82 float tolerance) noexcept {
83 if (x < candidate.left_px)
85 if (x >= candidate.right_px)
87
88 const auto leading = std::abs(static_cast<double>(x) - static_cast<double>(candidate.left_px));
89 const auto trailing =
90 std::abs(static_cast<double>(x) - static_cast<double>(candidate.right_px));
91 const auto width =
92 static_cast<double>(candidate.right_px) - static_cast<double>(candidate.left_px);
93 const auto edge_band = std::min(static_cast<double>(tolerance), width / 3.0);
94
95 if (leading <= edge_band)
97 if (trailing <= edge_band)
100}
101
102} // namespace hit_test_detail
103
122[[nodiscard]] inline std::optional<ProjectedHit>
123hit_test_projected_items(std::span<const ProjectedHitCandidate> back_to_front, float x_px,
124 float y_px, float tolerance_px) noexcept {
125 if (!std::isfinite(x_px) || !std::isfinite(y_px) || !std::isfinite(tolerance_px) ||
126 tolerance_px < 0.0f)
127 return std::nullopt;
128
129 std::optional<ProjectedHit> best;
130 auto best_distance_squared = std::numeric_limits<double>::infinity();
131 auto best_contains = false;
132
133 for (const auto& candidate : back_to_front) {
134 if (!hit_test_detail::valid_candidate(candidate) ||
135 !hit_test_detail::inside_expanded_half_open(x_px, candidate.left_px, candidate.right_px,
136 tolerance_px) ||
137 !hit_test_detail::inside_expanded_half_open(y_px, candidate.top_px, candidate.bottom_px,
138 tolerance_px))
139 continue;
140
141 const auto dx = hit_test_detail::distance_to_half_open_span(x_px, candidate.left_px,
142 candidate.right_px);
143 const auto dy = hit_test_detail::distance_to_half_open_span(y_px, candidate.top_px,
144 candidate.bottom_px);
145 const auto distance_squared = dx * dx + dy * dy;
146 const auto contains = hit_test_detail::inside_half_open(candidate, x_px, y_px);
147
148 const auto distance_tie = distance_squared == best_distance_squared;
149 const auto wins_boundary_tie = distance_tie && contains && !best_contains;
150 const auto wins_paint_order_tie = distance_tie && contains == best_contains;
151 if (!best || distance_squared < best_distance_squared || wins_boundary_tie ||
152 wins_paint_order_tie) {
153 best_distance_squared = distance_squared;
154 best_contains = contains;
155 best = ProjectedHit{candidate.item,
156 hit_test_detail::region_at(candidate, x_px, tolerance_px)};
157 }
158 }
159
160 return best;
161}
162
164
165} // namespace pulp::timeline_editor
std::optional< ProjectedHit > hit_test_projected_items(std::span< const ProjectedHitCandidate > back_to_front, float x_px, float y_px, float tolerance_px) noexcept
Selects one projected timeline item using already-resolved pixel tolerance.
Definition hit_test.hpp:123
ProjectedHitRegion
The horizontal part of a projected item selected by a hit test.
Definition hit_test.hpp:37
@ LeadingEdge
Left/start edge in projected timeline space.
Definition hit_test.hpp:39
@ Body
Definition hit_test.hpp:38
@ TrailingEdge
Right/end edge in projected timeline space.
Definition hit_test.hpp:40
Definition hit_test.hpp:51
bool inside_half_open(const ProjectedHitCandidate &candidate, float x, float y) noexcept
Definition hit_test.hpp:68
double distance_to_half_open_span(float value, float start, float end) noexcept
Definition hit_test.hpp:73
ProjectedHitRegion region_at(const ProjectedHitCandidate &candidate, float x, float tolerance) noexcept
Definition hit_test.hpp:81
bool valid_candidate(const ProjectedHitCandidate &candidate) noexcept
Definition hit_test.hpp:53
bool inside_expanded_half_open(float value, float start, float end, float tolerance) noexcept
Definition hit_test.hpp:60
ScriptedUiHost is a SequencerUiHost whose playhead is written by the caller instead of an engine,...
Definition edit_intent.hpp:14
Nonzero monotonic identity for a Timeline-owned object.
Definition item_id.hpp:19
One timeline item after a front-end projects it into pixel space.
Definition hit_test.hpp:26
float left_px
Definition hit_test.hpp:28
float bottom_px
Definition hit_test.hpp:31
timeline::ItemId item
Definition hit_test.hpp:27
constexpr bool operator==(const ProjectedHitCandidate &) const =default
float top_px
Definition hit_test.hpp:29
float right_px
Definition hit_test.hpp:30
The identity and region selected from a projected candidate set.
Definition hit_test.hpp:44
timeline::ItemId item
Definition hit_test.hpp:45
constexpr bool operator==(const ProjectedHit &) const =default
ProjectedHitRegion region
Definition hit_test.hpp:46