fix(plot,glfw): layer the crosshair under the handles, sync it across a figure, and harden the lookup
This commit is contained in:
1 parent
8e27e22ebe
commit
29824b6919
5 files changed
+183
-63
No files matched your search
@@ -215,6 +215,12 @@ bool Host::run(const RunConfig& cfg, const FrameFn& frame, Status* st) {
|
||||
for (u32 i = 0; i < row_controllers_.size(); ++i) {
|
||||
row_controllers_[i]->set_viewport(figure_->layout().rows[i]);
|
||||
}
|
||||
// One hover, every row (spec §15): the crosshair the row controller under the pointer
|
||||
// placed is applied to the whole figure here, after the pointers of this frame have been
|
||||
// handled and before anything lays the rows out -- so every row's readout lists its own
|
||||
// signals at the one x the pointer is over. In the run loop rather than in a callback, so a
|
||||
// scripted run that injects its events straight into a row controller is synced too.
|
||||
sync_figure_crosshair();
|
||||
} else {
|
||||
controller_.set_viewport(viewport); // also Plot::set_viewport, then layout()
|
||||
}
|
||||
@@ -406,6 +412,10 @@ void Host::set_figure(Figure* figure) noexcept {
|
||||
figure_ = figure;
|
||||
row_controllers_.clear();
|
||||
last_row_ = 0u;
|
||||
// A new figure holds no crosshair this host has spread: the comparison in
|
||||
// `sync_figure_crosshair()` starts from "nothing spread", which is the state its rows are in.
|
||||
spread_active_ = false;
|
||||
spread_x_ = 0.0;
|
||||
if (figure_ == nullptr) return;
|
||||
const u32 rows = figure_->row_count();
|
||||
row_controllers_.reserve(rows);
|
||||
@@ -420,9 +430,20 @@ input::Controller* Host::row_controller(u32 row) noexcept {
|
||||
|
||||
input::Controller& Host::controller_at(f32 x, f32 y) noexcept {
|
||||
if (figure_ == nullptr || row_controllers_.empty()) return controller_;
|
||||
const FigureLayout& layout = figure_->layout();
|
||||
const std::vector<Rect>& rows = layout.rows;
|
||||
if (rows.empty() || rows.size() != row_controllers_.size()) return controller_;
|
||||
const u32 row = row_at(x, y);
|
||||
if (row >= row_controllers_.size()) return controller_;
|
||||
last_row_ = row;
|
||||
input::Controller& controller = *row_controllers_[row];
|
||||
// The row's own viewport, so the geometry this controller hit-tests is the geometry
|
||||
// the last frame drew: the same rect `Figure::layout()` gave the row's plot.
|
||||
controller.set_viewport(figure_->layout().rows[row]);
|
||||
return controller;
|
||||
}
|
||||
|
||||
u32 Host::row_at(f32 x, f32 y) const noexcept {
|
||||
if (figure_ == nullptr || row_controllers_.empty()) return 0u;
|
||||
const std::vector<Rect>& rows = figure_->layout().rows;
|
||||
if (rows.empty() || rows.size() != row_controllers_.size()) 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
|
||||
@@ -442,12 +463,46 @@ input::Controller& Host::controller_at(f32 x, f32 y) noexcept {
|
||||
}
|
||||
}
|
||||
(void)x; // the rows tile the figure vertically, so y alone picks one
|
||||
last_row_ = row;
|
||||
input::Controller& controller = *row_controllers_[row];
|
||||
// The row's own viewport, so the geometry this controller hit-tests is the geometry
|
||||
// the last frame drew: the same rect `Figure::layout()` gave the row's plot.
|
||||
controller.set_viewport(rows[row]);
|
||||
return controller;
|
||||
return row;
|
||||
}
|
||||
|
||||
void Host::sync_figure_crosshair() noexcept {
|
||||
const u32 rows = figure_ != nullptr ? figure_->row_count() : 0u;
|
||||
if (rows == 0u || row_controllers_.empty()) return;
|
||||
|
||||
// Which row's crosshair is the hover. With a pointer of the host's own it is the row that pointer
|
||||
// is over -- the same rule events are routed by. A scripted run injects its events straight into
|
||||
// a row controller and the host never sees a pointer, so there the hover is the row whose
|
||||
// crosshair is *not* the one this host spread last: the figure writes one value to every row, so
|
||||
// any row holding something else was changed from outside, and that is the one the run touched.
|
||||
// Nothing differing means every row still holds the last spread -- no new hover -- and the figure
|
||||
// keeps it.
|
||||
u32 hovered = rows;
|
||||
if (have_pointer_) {
|
||||
hovered = row_at(last_pointer_x_, last_pointer_y_);
|
||||
} else {
|
||||
for (u32 i = 0; i < rows; ++i) {
|
||||
const Crosshair& candidate = figure_->plot(i).crosshair();
|
||||
if (candidate.active != spread_active_ || (candidate.active && candidate.x != spread_x_)) {
|
||||
hovered = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hovered >= rows) return;
|
||||
}
|
||||
|
||||
// Applied to the whole figure: one x, every row. A row whose controller has *no* crosshair (the
|
||||
// pointer left its axes, which is the only thing that clears one) clears the figure's, so the
|
||||
// rows cannot be left showing a hover the pointer has abandoned.
|
||||
const Plot& row = figure_->plot(hovered);
|
||||
if (row.crosshair().active) {
|
||||
figure_->set_crosshair(row.crosshair().x, true);
|
||||
spread_x_ = row.crosshair().x;
|
||||
spread_active_ = true;
|
||||
} else {
|
||||
figure_->clear_crosshair();
|
||||
spread_active_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace plot::glfw
|
||||
Reference in new issue
Block a user