test(input): region, cursor and band-edge direct-manipulation cases (spec §5)
This commit is contained in:
1 parent
e4d48cc6c3
commit
f423cf6f99
3 files changed
+819
No files matched your search
@@ -721,3 +721,375 @@ PLOT_TEST(live_gestures_survive_a_degenerate_range) {
|
||||
PLOT_CHECK_NEAR(f.xr().max, f.newest() - f.graph.x_offset(f.ax), 1e-9);
|
||||
PLOT_CHECK(f.graph.x_follow(f.ax) == plot::XFollow::Live);
|
||||
}
|
||||
|
||||
// --- direct manipulation: grabbable cursors, regions and band edges (spec §5) ---
|
||||
|
||||
namespace {
|
||||
|
||||
/// The x column a region's bound sits at, as `Plot::layout()` measured it -- the same
|
||||
/// number the controller hit-tests and the renderer draws the bound in. Negative when
|
||||
/// the layout holds no such region, which no caller here should see.
|
||||
f32 bound_x(plot::Plot& p, u32 region, bool left) {
|
||||
for (const plot::RegionLayout& r : p.layout().regions) {
|
||||
if (r.region != region) continue;
|
||||
return plot::handle_column(left ? r.left_handle : r.right_handle);
|
||||
}
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
/// A region whose bounds are stored exactly as written: `Free` snap, so a drag's
|
||||
/// endpoints are the numbers the gesture put there rather than the sample grid
|
||||
/// `add_region()` pulls a snapping region onto.
|
||||
plot::Region free_region(f64 x0, f64 x1) {
|
||||
plot::Region r;
|
||||
r.x0 = x0;
|
||||
r.x1 = x1;
|
||||
r.snap = plot::RegionSnap::Free;
|
||||
return r;
|
||||
}
|
||||
|
||||
/// The data x under one device-pixel column.
|
||||
f64 data_at(const plot::Plot& p, u32 axis, f32 px) {
|
||||
return p.x_transform(axis).to_data_x(static_cast<f64>(px));
|
||||
}
|
||||
|
||||
/// The device column a data x is drawn at.
|
||||
f32 column_of(const plot::Plot& p, u32 axis, f64 data) {
|
||||
return static_cast<f32>(p.x_transform(axis).to_screen_x(data));
|
||||
}
|
||||
|
||||
void hover(plot::input::Controller& c, f32 px, f32 py = 200.0f, u32 mods = 0) {
|
||||
c.on_pointer({px, py, 0.0f, 0.0f, plot::input::Button::Left, mods, false, false, false});
|
||||
}
|
||||
void press(plot::input::Controller& c, f32 px, f32 py = 200.0f, u32 mods = 0) {
|
||||
c.on_pointer({px, py, 0.0f, 0.0f, plot::input::Button::Left, mods, true, true, false});
|
||||
}
|
||||
void move_to(plot::input::Controller& c, f32 px, f32 py = 200.0f, u32 mods = 0) {
|
||||
c.on_pointer({px, py, 0.0f, 0.0f, plot::input::Button::Left, mods, true, false, false});
|
||||
}
|
||||
void release(plot::input::Controller& c, f32 px, f32 py = 200.0f, u32 mods = 0) {
|
||||
c.on_pointer({px, py, 0.0f, 0.0f, plot::input::Button::Left, mods, false, false, true});
|
||||
}
|
||||
|
||||
/// Root-mean-square of the y values of the `y(i) == i` samples in [begin, end]: what
|
||||
/// the band case's measurement has to match exactly.
|
||||
f64 ramp_rms(u32 begin, u32 end) {
|
||||
f64 sq = 0.0;
|
||||
for (u32 i = begin; i <= end; ++i) sq += static_cast<f64>(i) * static_cast<f64>(i);
|
||||
return std::sqrt(sq / static_cast<f64>(end - begin + 1u));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// A marker is a handle like any other and needs no modifier to be moved: the hit
|
||||
/// test runs before the pan is ever considered, so a press on a cursor is that
|
||||
/// cursor's drag while a press anywhere else still pans. `Ctrl` is not a second
|
||||
/// gesture -- it must not turn a press on a marker into anything else, which is the
|
||||
/// regression to watch for.
|
||||
PLOT_TEST(cursor_is_grabbed_without_a_modifier) {
|
||||
Fixture f;
|
||||
const u32 cid = f.plot.add_cursor(50.0, plot::Color::hex(0x00FF00));
|
||||
const f32 sx = screen_x_of(f.plot, f.ax, 50.0);
|
||||
const f64 range_min = f.plot.x_range(f.ax).min;
|
||||
|
||||
press(f.controller, sx);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveCursor);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == cid);
|
||||
// The plot publishes the same answers, for the renderer and for a host that wants
|
||||
// a resize cursor or a status line of its own.
|
||||
PLOT_CHECK(f.plot.hot_handles().cursor == cid);
|
||||
PLOT_CHECK(f.plot.hot_handles().grabbed_cursor == cid);
|
||||
move_to(f.controller, sx + 100.0f);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(cid).x, data_at(f.plot, f.ax, sx + 100.0f), 1e-9);
|
||||
PLOT_CHECK(f.plot.cursor(cid).x > 50.0);
|
||||
// The view did not pan: the gesture belonged to the marker, not to the ranges.
|
||||
PLOT_CHECK_NEAR(f.plot.x_range(f.ax).min, range_min, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.x_range(f.ax).span(), 100.0, 1e-9);
|
||||
release(f.controller, sx + 100.0f);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::None);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == plot::kInvalid);
|
||||
PLOT_CHECK(f.plot.hot_handles().grabbed_cursor == plot::kInvalid);
|
||||
|
||||
// The same press with ctrl is the same gesture, not a different mode.
|
||||
const f32 moved_to = column_of(f.plot, f.ax, f.plot.cursor(cid).x);
|
||||
const f64 min_before = f.plot.x_range(f.ax).min;
|
||||
press(f.controller, moved_to, 200.0f, kCtrl);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveCursor);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == cid);
|
||||
move_to(f.controller, moved_to - 40.0f, 200.0f, kCtrl);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(cid).x, data_at(f.plot, f.ax, moved_to - 40.0f), 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.x_range(f.ax).min, min_before, 1e-9); // still no pan
|
||||
release(f.controller, moved_to - 40.0f, 200.0f, kCtrl);
|
||||
}
|
||||
|
||||
/// The other half of "a superset of the old gesture": a press that is on no handle
|
||||
/// reaches the pan exactly as it did before, with and without ctrl, and neither the
|
||||
/// cursor nor the region under the view is disturbed.
|
||||
PLOT_TEST(pressing_away_from_a_handle_still_pans) {
|
||||
Fixture f;
|
||||
const u32 cid = f.plot.add_cursor(20.0, plot::Color::hex(0x00FF00));
|
||||
const u32 rid = f.plot.add_region(free_region(40.0, 60.0));
|
||||
|
||||
for (const u32 mods : {0u, kCtrl}) {
|
||||
const f32 px = screen_x_of(f.plot, f.ax, 85.0);
|
||||
const f64 grabbed = data_at(f.plot, f.ax, px);
|
||||
const f64 min_before = f.plot.x_range(f.ax).min;
|
||||
press(f.controller, px, 200.0f, mods);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::Pan);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == plot::kInvalid);
|
||||
PLOT_CHECK(f.controller.grabbed_region() == plot::kInvalid);
|
||||
PLOT_CHECK(f.plot.hot_handles().cursor == plot::kInvalid);
|
||||
PLOT_CHECK(f.plot.hot_handles().region == plot::kInvalid);
|
||||
// The pan is driven by the event's own delta, so this motion has to report one:
|
||||
// a host that sends an absolute position with dx = 0 has not moved the drag.
|
||||
f.controller.on_pointer(
|
||||
{px + 70.0f, 200.0f, 70.0f, 0.0f, plot::input::Button::Left, mods, true, false, false});
|
||||
release(f.controller, px + 70.0f, 200.0f, mods);
|
||||
// Grab-and-drag, the invariant the pan has always held: the data under the press
|
||||
// is under the pointer afterwards.
|
||||
PLOT_CHECK_NEAR(data_at(f.plot, f.ax, px + 70.0f), grabbed, 1e-9);
|
||||
PLOT_CHECK(f.plot.x_range(f.ax).min < min_before);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(cid).x, 20.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x0, 40.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 60.0, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
/// Nearest-first resolution: a bound within the grab radius beats the body it sits
|
||||
/// inside, and the body answers only away from both bounds. The bounds are *inside*
|
||||
/// the fill, so a body-first hit test would report `Body` here and a region could
|
||||
/// never be resized.
|
||||
PLOT_TEST(region_edge_wins_over_region_body) {
|
||||
Fixture f;
|
||||
const u32 rid = f.plot.add_region(free_region(30.0, 70.0));
|
||||
const f32 left = bound_x(f.plot, rid, true);
|
||||
const f32 right = bound_x(f.plot, rid, false);
|
||||
PLOT_CHECK(left >= 0.0f && right > left);
|
||||
const f32 mid = 0.5f * (left + right);
|
||||
|
||||
// No pointer seen yet: nothing is on anything.
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::None);
|
||||
|
||||
hover(f.controller, mid);
|
||||
PLOT_CHECK(f.controller.hovered_region() == rid);
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::Body);
|
||||
PLOT_CHECK(f.plot.hot_handles().region == rid);
|
||||
PLOT_CHECK(f.plot.hot_handles().zone == plot::RegionGrab::Body);
|
||||
|
||||
hover(f.controller, left);
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::Left);
|
||||
PLOT_CHECK(f.plot.hot_handles().zone == plot::RegionGrab::Left);
|
||||
hover(f.controller, right);
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::Right);
|
||||
PLOT_CHECK(f.plot.hot_handles().zone == plot::RegionGrab::Right);
|
||||
// A couple of pixels inside the bound is still the bound, not the body.
|
||||
hover(f.controller, left + 2.0f);
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::Left);
|
||||
// Just outside the fill nothing is hot, so the `Body` answer above is not an
|
||||
// accident of treating a wide neighbourhood as the region.
|
||||
hover(f.controller, left - 12.0f);
|
||||
PLOT_CHECK(f.controller.hovered_region() == plot::kInvalid);
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::None);
|
||||
|
||||
// Each bound opens the drag that moves that bound; the body opens the one that
|
||||
// moves the interval.
|
||||
press(f.controller, left);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::ResizeRegionLeft);
|
||||
PLOT_CHECK(f.controller.grabbed_region() == rid);
|
||||
release(f.controller, left);
|
||||
press(f.controller, right);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::ResizeRegionRight);
|
||||
release(f.controller, right);
|
||||
press(f.controller, mid);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveRegion);
|
||||
release(f.controller, mid);
|
||||
PLOT_CHECK(f.controller.grabbed_region() == plot::kInvalid);
|
||||
|
||||
// A cursor marker is hit-tested before any region, so a press on it is still the
|
||||
// cursor's drag.
|
||||
const u32 cid = f.plot.add_cursor(50.0, plot::Color::hex(0x00FF00));
|
||||
const f32 marker = screen_x_of(f.plot, f.ax, 50.0);
|
||||
hover(f.controller, marker);
|
||||
// The region is under the pointer too -- the marker sits inside its interval, so the
|
||||
// region's answer is `Body` -- and the cursor still wins the drag: the two hit tests
|
||||
// are separate, and the cursor's is the one that decides the gesture.
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::Body);
|
||||
PLOT_CHECK(f.controller.hovered_region() == rid);
|
||||
press(f.controller, marker);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveCursor);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == cid);
|
||||
PLOT_CHECK(f.controller.grabbed_region() == plot::kInvalid);
|
||||
release(f.controller, marker);
|
||||
}
|
||||
|
||||
/// A body drag translates both bounds by the *same* data delta -- the width is
|
||||
/// preserved exactly -- and each motion is a function of the pointer rather than a
|
||||
/// step from the last one, so a pointer that returns to where it started returns the
|
||||
/// region there too even when the host's reported deltas say otherwise.
|
||||
PLOT_TEST(region_body_move_preserves_width) {
|
||||
Fixture f;
|
||||
const u32 rid = f.plot.add_region(free_region(30.0, 70.0));
|
||||
const f64 width = f.plot.region(rid).x1 - f.plot.region(rid).x0;
|
||||
const f32 mid = 0.5f * (bound_x(f.plot, rid, true) + bound_x(f.plot, rid, false));
|
||||
|
||||
press(f.controller, mid);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveRegion);
|
||||
PLOT_CHECK(f.controller.grabbed_region() == rid);
|
||||
move_to(f.controller, mid + 120.0f);
|
||||
const f64 delta = data_at(f.plot, f.ax, mid + 120.0f) - data_at(f.plot, f.ax, mid);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x0, 30.0 + delta, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 70.0 + delta, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1 - f.plot.region(rid).x0, width, 1e-9);
|
||||
PLOT_CHECK(f.plot.region(rid).x0 > 30.0);
|
||||
|
||||
// The deltas here are a lie -- 0, 0 while the pointer moved back -- and the bounds
|
||||
// are rebuilt from the pointer's own coordinate, so nothing accumulates.
|
||||
f.controller.on_pointer({mid, 200.0f, 0.0f, 0.0f, plot::input::Button::Left, 0u, true, false, false});
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x0, 30.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 70.0, 1e-9);
|
||||
release(f.controller, mid);
|
||||
}
|
||||
|
||||
/// A bound dragged past its partner *swaps* the interval rather than inverting or
|
||||
/// clamping it: the store keeps (min, max), so the region always has a positive
|
||||
/// width, and the pointer keeps hold of the bound it grabbed -- which, having crossed
|
||||
/// over, is now the other one.
|
||||
PLOT_TEST(region_bounds_stay_ordered_when_dragged_past_each_other) {
|
||||
Fixture f;
|
||||
const u32 rid = f.plot.add_region(free_region(30.0, 70.0));
|
||||
const f32 right = bound_x(f.plot, rid, false);
|
||||
|
||||
// Drag the right bound well past the left one.
|
||||
const f32 to = column_of(f.plot, f.ax, 12.0);
|
||||
const f64 want = data_at(f.plot, f.ax, to);
|
||||
PLOT_CHECK(want < 30.0); // the drag really does cross the other bound
|
||||
press(f.controller, right);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::ResizeRegionRight);
|
||||
move_to(f.controller, to);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x0, want, 1e-9); // the dragged bound, now the lower one
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 30.0, 1e-9); // the one it was dragged past
|
||||
PLOT_CHECK(f.plot.region(rid).x0 < f.plot.region(rid).x1);
|
||||
|
||||
// The grabbed bound is still the one being moved -- it is `x0` now, and the next
|
||||
// motion moves it without the gesture having to be restarted.
|
||||
const f32 back = column_of(f.plot, f.ax, 20.0);
|
||||
move_to(f.controller, back);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x0, data_at(f.plot, f.ax, back), 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 30.0, 1e-9);
|
||||
release(f.controller, back);
|
||||
|
||||
// And the mirror image: the left bound dragged past the right one.
|
||||
f.plot.set_region(rid, 30.0, 70.0);
|
||||
const f32 left = bound_x(f.plot, rid, true);
|
||||
const f32 far = column_of(f.plot, f.ax, 90.0);
|
||||
const f64 want_far = data_at(f.plot, f.ax, far);
|
||||
press(f.controller, left);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::ResizeRegionLeft);
|
||||
move_to(f.controller, far);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x0, 70.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1, want_far, 1e-9);
|
||||
PLOT_CHECK(f.plot.region(rid).x0 < f.plot.region(rid).x1);
|
||||
release(f.controller, far);
|
||||
}
|
||||
|
||||
/// A region whose two bounds share a column cannot be grabbed by an edge -- an edge
|
||||
/// would win the hit test every time and the region could never be moved again once
|
||||
/// collapsed -- so it is grabbed by its body, which keeps it movable. The threshold is
|
||||
/// the narrowest region whose bounds a pointer can tell apart: below it the body
|
||||
/// answers, above it the bound does.
|
||||
PLOT_TEST(collapsed_region_cannot_be_grabbed_by_an_edge) {
|
||||
Fixture f;
|
||||
const u32 rid = f.plot.add_region(free_region(50.0, 50.0));
|
||||
const f32 col = bound_x(f.plot, rid, true);
|
||||
PLOT_CHECK_NEAR(static_cast<f64>(col), static_cast<f64>(bound_x(f.plot, rid, false)), 1e-6);
|
||||
hover(f.controller, col);
|
||||
PLOT_CHECK(f.controller.region_grab(rid) == plot::RegionGrab::Body);
|
||||
press(f.controller, col);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveRegion);
|
||||
move_to(f.controller, col + 60.0f);
|
||||
const f64 delta = data_at(f.plot, f.ax, col + 60.0f) - data_at(f.plot, f.ax, col);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x0, 50.0 + delta, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.region(rid).x1, 50.0 + delta, 1e-9); // moved, still collapsed
|
||||
release(f.controller, col + 60.0f);
|
||||
|
||||
// Two pixels wide is still narrower than the minimum (3 logical px at scale 1): the
|
||||
// same answer at either bound.
|
||||
const u32 narrow = f.plot.add_region(free_region(data_at(f.plot, f.ax, 400.0f),
|
||||
data_at(f.plot, f.ax, 402.0f)));
|
||||
hover(f.controller, 400.0f);
|
||||
PLOT_CHECK(f.controller.region_grab(narrow) == plot::RegionGrab::Body);
|
||||
hover(f.controller, 401.0f);
|
||||
PLOT_CHECK(f.controller.region_grab(narrow) == plot::RegionGrab::Body);
|
||||
press(f.controller, 401.0f);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveRegion);
|
||||
release(f.controller, 401.0f);
|
||||
|
||||
// Six pixels is wide enough to tell the bounds apart, and there the bound wins.
|
||||
const u32 wide = f.plot.add_region(free_region(data_at(f.plot, f.ax, 400.0f),
|
||||
data_at(f.plot, f.ax, 406.0f)));
|
||||
hover(f.controller, 400.0f);
|
||||
PLOT_CHECK(f.controller.region_grab(wide) == plot::RegionGrab::Left);
|
||||
hover(f.controller, 406.0f);
|
||||
PLOT_CHECK(f.controller.region_grab(wide) == plot::RegionGrab::Right);
|
||||
}
|
||||
|
||||
/// The shaded measurement band's two edges *are* the cursors that bound it, so
|
||||
/// dragging one is the cursor drag with a different hit target: the grab maps to the
|
||||
/// cursor owning that edge, the other edge stays where it was, and `Plot::measurement`
|
||||
/// -- which the demo and the renderer both read -- follows as it already does.
|
||||
PLOT_TEST(band_edge_drag_moves_the_owning_cursor_and_updates_the_measurement) {
|
||||
constexpr u32 kSamples = 100u;
|
||||
std::vector<f32> samples(kSamples);
|
||||
for (u32 i = 0; i < kSamples; ++i) samples[i] = static_cast<f32>(i);
|
||||
plot::data::ArraySeries s(std::move(samples), 0.0, 1.0);
|
||||
|
||||
Fixture f;
|
||||
f.ay = f.plot.add_y_axis({"v", {0.0, 99.0}, false, plot::AxisPlace::Left, false});
|
||||
const u32 tr = f.plot.add_trace({&s, plot::Color::hex(0x40C0FF), 1.5f, f.ax, f.ay});
|
||||
const u32 a = f.plot.add_cursor(10.0, plot::Color::hex(0x00FF00));
|
||||
const u32 b = f.plot.add_cursor(60.0, plot::Color::hex(0xFF00FF));
|
||||
const u32 m = f.plot.add_measure(a, b, tr);
|
||||
PLOT_CHECK(f.plot.measurement(m).valid);
|
||||
PLOT_CHECK_NEAR(f.plot.measurement(m).mean, 35.0, 1e-9); // samples 10..60
|
||||
PLOT_CHECK_NEAR(f.plot.measurement(m).dx, 50.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.measurement(m).ymax, 60.0, 1e-9);
|
||||
|
||||
// The band's left edge is cursor `a` at its own column, and the drag moves that
|
||||
// cursor -- not the band, not the view. The pointer lands *between* two samples and
|
||||
// the readout is over the nearest sample to each cursor, which is the measurement's
|
||||
// documented rule: a cursor at 20.3 measures from the sample at x == 20.
|
||||
const f32 edge_a = screen_x_of(f.plot, f.ax, 10.0);
|
||||
const f32 to = column_of(f.plot, f.ax, 20.3);
|
||||
press(f.controller, edge_a);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveCursor);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == a);
|
||||
move_to(f.controller, to);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(a).x, 20.3, 1e-3); // the bound followed the pointer
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(b).x, 60.0, 1e-9); // the other edge did not move
|
||||
const plot::Measurement moved = f.plot.measurement(m);
|
||||
PLOT_CHECK(moved.valid);
|
||||
PLOT_CHECK_NEAR(moved.dx, 40.0, 1e-9); // samples 20..60: 41 of them, dx == 1
|
||||
PLOT_CHECK_NEAR(moved.mean, 40.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(moved.ymin, 20.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(moved.ymax, 60.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(moved.rms, ramp_rms(20u, 60u), 1e-9);
|
||||
PLOT_CHECK_NEAR(moved.integral, static_cast<f64>((20 + 60) * 41 / 2), 1e-9);
|
||||
release(f.controller, to);
|
||||
|
||||
// The right edge is cursor `b`, and grabbing it moves `b` alone.
|
||||
const f32 edge_b = screen_x_of(f.plot, f.ax, 60.0);
|
||||
const f32 to_b = column_of(f.plot, f.ax, 50.3);
|
||||
press(f.controller, edge_b);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveCursor);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == b);
|
||||
move_to(f.controller, to_b);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(b).x, 50.3, 1e-3);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(a).x, 20.3, 1e-3);
|
||||
const plot::Measurement narrowed = f.plot.measurement(m);
|
||||
PLOT_CHECK(narrowed.valid);
|
||||
PLOT_CHECK_NEAR(narrowed.dx, 30.0, 1e-9); // samples 20..50: 31 of them
|
||||
PLOT_CHECK_NEAR(narrowed.mean, 35.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(narrowed.ymin, 20.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(narrowed.ymax, 50.0, 1e-9);
|
||||
release(f.controller, to_b);
|
||||
}
|
||||
Reference in new issue
Block a user