adapters: route pointer events by cell rect
This commit is contained in:
1 parent
7d8c453232
commit
e5b7faadec
6 files changed
+145
-93
No files matched your search
+25
-21
@@ -455,9 +455,11 @@ void Host::set_figure(Figure* figure) noexcept {
|
||||
row_rects_.clear(); // no figure, no rows: the handlers fall back to `controller_`
|
||||
return;
|
||||
}
|
||||
const u32 rows = figure_->row_count();
|
||||
row_controllers_.reserve(rows);
|
||||
for (u32 i = 0; i < rows; ++i) {
|
||||
// One controller per **plot** (a row of several cells has one for each), indexed by the
|
||||
// flattened plot index the figure uses.
|
||||
const u32 plots = figure_->plot_count();
|
||||
row_controllers_.reserve(plots);
|
||||
for (u32 i = 0; i < plots; ++i) {
|
||||
row_controllers_.push_back(std::make_unique<input::Controller>(figure_->plot(i)));
|
||||
}
|
||||
// The new figure's rects go into the cache now, not at the next frame: an event
|
||||
@@ -469,8 +471,10 @@ void Host::set_figure(Figure* figure) noexcept {
|
||||
|
||||
void Host::refresh_row_rects() noexcept {
|
||||
// The one `Figure::layout()` per call: every event handler reads `row_rects_`
|
||||
// instead of paying this again, which is the whole point of the cache (R7).
|
||||
row_rects_ = figure_ != nullptr ? figure_->layout().rows : std::vector<Rect>{};
|
||||
// instead of paying this again, which is the whole point of the cache (R7). The rects
|
||||
// are the figure's **cells** (one per plot, flattened), which for a figure with one
|
||||
// cell per row are exactly its row rects.
|
||||
row_rects_ = figure_ != nullptr ? figure_->layout().cells : std::vector<Rect>{};
|
||||
}
|
||||
|
||||
input::Controller* Host::row_controller(u32 row) noexcept {
|
||||
@@ -486,8 +490,7 @@ input::Controller& Host::controller_at(f32 x, f32 y) noexcept {
|
||||
// frame stale, which is the documented worst case.
|
||||
const std::vector<Rect>& rows = row_rects_;
|
||||
if (rows.empty() || rows.size() != row_controllers_.size()) return controller_;
|
||||
(void)x; // the rows tile the figure vertically, so y alone picks one -- see row_in()
|
||||
const u32 row = row_in(rows, y);
|
||||
const u32 row = row_in(rows, x, y);
|
||||
last_row_ = row;
|
||||
input::Controller& controller = *row_controllers_[row];
|
||||
// The row's own viewport, so the geometry this controller hit-tests is the geometry
|
||||
@@ -506,7 +509,6 @@ void Host::feed_pointer_event(const input::PointerEvent& e) noexcept {
|
||||
|
||||
input::Controller& Host::pointer_target(f32 x, f32 y, input::Button button, bool pressed,
|
||||
bool released) noexcept {
|
||||
(void)x; // the rows tile the figure vertically, so y alone picks one -- see row_in()
|
||||
if (figure_ == nullptr || row_controllers_.empty()) return controller_;
|
||||
const std::vector<Rect>& rows = row_rects_;
|
||||
if (rows.empty() || rows.size() != row_controllers_.size()) return controller_;
|
||||
@@ -523,7 +525,7 @@ input::Controller& Host::pointer_target(f32 x, f32 y, input::Button button, bool
|
||||
// drag, and its press must not steal the capture to the row it landed in.
|
||||
if (released && button == captured_button_) captured_ = false;
|
||||
} else {
|
||||
row = row_in(rows, y);
|
||||
row = row_in(rows, x, y);
|
||||
if (pressed) {
|
||||
captured_ = true;
|
||||
captured_row_ = row;
|
||||
@@ -541,27 +543,29 @@ input::Controller& Host::pointer_target(f32 x, f32 y, input::Button button, bool
|
||||
|
||||
u32 Host::pointer_row(const std::vector<Rect>& rows) noexcept {
|
||||
if (captured_ && captured_row_ < rows.size()) return captured_row_;
|
||||
return row_in(rows, last_pointer_y_);
|
||||
return row_in(rows, last_pointer_x_, last_pointer_y_);
|
||||
}
|
||||
|
||||
u32 Host::row_in(const std::vector<Rect>& rows, f32 y) noexcept {
|
||||
u32 Host::row_in(const std::vector<Rect>& rows, f32 x, f32 y) noexcept {
|
||||
if (rows.empty()) return 0u;
|
||||
// The row the pixel is in, or -- when it is in none of them, which is the row gap a
|
||||
// host has asked for with `Figure::set_row_gap()` or the figure's own margins -- the
|
||||
// row whose rect is nearest. Selection by distance rather than by "the first that
|
||||
// matches" is what makes a pixel in a gap belong to the row it is *near*: the rows
|
||||
// tile the figure at the default gap of 0, so this is unreachable until a host asks
|
||||
// for one, and a gesture in a visible gap must not be routed to row 0 by default.
|
||||
// The cell the pixel is in, or -- when it is in none of them, which is the row gap or
|
||||
// column gap a host has asked for with `Figure::set_row_gap()`/`set_col_gap()` or the
|
||||
// figure's own margins -- the cell whose rect is nearest (squared distance from the
|
||||
// pixel to the rect). Selection by distance rather than by "the first that matches" is
|
||||
// what makes a pixel in a gap belong to the cell it is *near*, and a gesture in a
|
||||
// visible gap must not be routed to plot 0 by default. For a figure with one cell per
|
||||
// row every cell spans the figure's width, so this reduces to the nearest row by y.
|
||||
u32 row = 0u;
|
||||
f32 best = std::numeric_limits<f32>::infinity();
|
||||
for (u32 i = 0; i < rows.size(); ++i) {
|
||||
const f32 top = rows[i].y;
|
||||
const f32 bottom = rows[i].y + rows[i].h;
|
||||
const f32 distance = y < top ? top - y : (y >= bottom ? y - bottom : 0.0f);
|
||||
const Rect& r = rows[i];
|
||||
const f32 dx = x < r.x ? r.x - x : (x >= r.right() ? x - r.right() : 0.0f);
|
||||
const f32 dy = y < r.y ? r.y - y : (y >= r.bottom() ? y - r.bottom() : 0.0f);
|
||||
const f32 distance = dx * dx + dy * dy;
|
||||
if (distance < best) {
|
||||
best = distance;
|
||||
row = i;
|
||||
if (distance == 0.0f) break; // inside this row: nothing can be nearer
|
||||
if (distance == 0.0f) break; // inside this cell: nothing can be nearer
|
||||
}
|
||||
}
|
||||
return row;
|
||||
|
||||
Reference in new issue
Block a user