feat(input): live x follow with relative zoom and mouse panning
This commit is contained in:
1 parent
eaae07d8e0
commit
77e861f79e
10 files changed
+971
-28
No files matched your search
@@ -18,7 +18,10 @@ constexpr f64 kZoomPerNotch = 0.15;
|
||||
constexpr f64 kKeyZoomNotches = 1.0;
|
||||
/// Zoom per pixel of a middle-drag along an axis strip. A pointer drag reports
|
||||
/// pixels, a different unit from a wheel notch, so it must not reuse
|
||||
/// kZoomPerNotch: 200 px of drag is one decade, exp(-200 * ln10/200) == 0.1.
|
||||
/// kZoomPerNotch: 200 px of drag is one decade, exp(-200 * ln10/200) == 0.1. The
|
||||
/// pixels are **logical** ones -- a distance the hand moves, like the tolerances
|
||||
/// below -- while the deltas that arrive are device pixels, so `axis_zoom()`
|
||||
/// converts through the plot's scale before applying this.
|
||||
constexpr f64 kZoomPerPixel = std::numbers::ln10 / 200.0;
|
||||
/// How close the pointer must be to a cursor to grab it, in **logical** pixels: the
|
||||
/// listener's aim is a physical distance, so it is converted to the device-pixel
|
||||
@@ -135,7 +138,7 @@ void Controller::on_wheel(const WheelEvent& raw) {
|
||||
|
||||
if (zoom_x) {
|
||||
for (u32 i = 0; i < plot_->x_axis_count(); ++i) {
|
||||
zoom_x_axis(i, plot_->x_transform(i).to_data_x(static_cast<f64>(e.x)), factor);
|
||||
zoom_x_axis(i, x_zoom_anchor(i, e.x, e.y), factor);
|
||||
}
|
||||
}
|
||||
if (zoom_y) {
|
||||
@@ -314,12 +317,17 @@ void Controller::move_cursor(const PointerEvent& e) {
|
||||
|
||||
void Controller::axis_zoom(const PointerEvent& e) {
|
||||
// A pointer drag reports pixels, not wheel notches, so it uses its own
|
||||
// sensitivity: 200 px is one decade.
|
||||
const f64 factor = std::exp(-static_cast<f64>(e.dy) * kZoomPerPixel);
|
||||
// sensitivity: 200 px is one decade. The gain is a physical distance the hand
|
||||
// moves, so the device-pixel delta is converted to logical pixels first -- the
|
||||
// same drag must zoom the same amount on a 2x display. A scale no host should
|
||||
// ever set (0, or negative) makes the factor non-positive, and the guard below
|
||||
// turns that into no zoom at all rather than an axis re-centred on infinity.
|
||||
const f64 logical_dy = static_cast<f64>(e.dy) / static_cast<f64>(plot_->ui_scale());
|
||||
const f64 factor = std::exp(-logical_dy * kZoomPerPixel);
|
||||
if (!(factor > 0.0)) return;
|
||||
if (mode_ == DragMode::AxisZoomX) {
|
||||
for (u32 i = 0; i < plot_->x_axis_count(); ++i) {
|
||||
zoom_x_axis(i, plot_->x_transform(i).to_data_x(static_cast<f64>(e.x)), factor);
|
||||
zoom_x_axis(i, x_zoom_anchor(i, e.x, e.y), factor);
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < plot_->y_axis_count(); ++i) {
|
||||
@@ -330,6 +338,23 @@ void Controller::axis_zoom(const PointerEvent& e) {
|
||||
|
||||
// --- helpers --------------------------------------------------------------
|
||||
|
||||
f64 Controller::x_zoom_anchor(u32 axis, f32 px, f32 py) const {
|
||||
// The pointer's own coordinate while it is over the data area -- the anchor a
|
||||
// zoom has always had, and the reason it feels attached to what is under the
|
||||
// cursor. Outside `axes` there is no data under the pointer at all, so the
|
||||
// coordinate it extrapolates to is a number the user cannot see; a live axis has
|
||||
// a better answer, its live edge, which makes a zoom from the label strip or from
|
||||
// off the plot keep the window ending at the newest sample instead of drifting
|
||||
// away from it. Any other axis keeps the extrapolated coordinate, and `Off` is
|
||||
// the default, so nothing changes for a plot that is not following.
|
||||
if (!std::as_const(*plot_).layout().axes.contains(px, py) &&
|
||||
plot_->x_follow(axis) == XFollow::Live) {
|
||||
f64 live = 0.0;
|
||||
if (plot_->x_live_edge(axis, live)) return live;
|
||||
}
|
||||
return plot_->x_transform(axis).to_data_x(static_cast<f64>(px));
|
||||
}
|
||||
|
||||
u64 Controller::cursor_at(f32 px, f32 py) const {
|
||||
const PlotLayout& lay = std::as_const(*plot_).layout();
|
||||
if (!lay.axes.contains(px, py)) return kInvalid;
|
||||
|
||||
Reference in new issue
Block a user