feat(plot): horizontal rulers alongside the vertical cursors
This commit is contained in:
1 parent
2d819c4228
commit
63f9655ae1
11 files changed
+514
-29
No files matched your search
@@ -759,6 +759,14 @@ f32 column_of(const plot::Plot& p, u32 axis, f64 data) {
|
||||
return static_cast<f32>(p.x_transform(axis).to_screen_x(data));
|
||||
}
|
||||
|
||||
/// The y twins of `data_at()`/`column_of()`: what a horizontal ruler is placed by and hit in.
|
||||
f64 data_at_row(const plot::Plot& p, u32 axis, f32 row) {
|
||||
return p.y_transform(axis).to_data_y(static_cast<f64>(row));
|
||||
}
|
||||
f32 row_of(const plot::Plot& p, u32 axis, f64 data) {
|
||||
return static_cast<f32>(p.y_transform(axis).to_screen_y(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});
|
||||
}
|
||||
@@ -823,6 +831,98 @@ PLOT_TEST(cursor_is_grabbed_without_a_modifier) {
|
||||
release(f.controller, moved_to - 40.0f, 200.0f, kCtrl);
|
||||
}
|
||||
|
||||
/// Spec §14 on the real pointer path: a horizontal ruler is grabbed by the same hit test as a
|
||||
/// marker -- no modifier, the same radius -- and the drag writes the coordinate its own
|
||||
/// orientation owns. The ruler's y follows the pointer's row; its x stays the dead field it is;
|
||||
/// a vertical cursor elsewhere on the plot is not disturbed; and the view does not pan.
|
||||
PLOT_TEST(horizontal_ruler_is_grabbed_and_dragged_along_y) {
|
||||
Fixture f;
|
||||
f.ay = f.plot.add_y_axis({"v", {0.0, 100.0}, false, plot::AxisPlace::Left, false});
|
||||
const u32 marker = f.plot.add_cursor(50.0, plot::Color::hex(0x00FF00));
|
||||
const u32 ruler = f.plot.add_h_ruler(40.0, plot::Color::hex(0x35D0A8));
|
||||
f.plot.layout();
|
||||
const f32 column = screen_x_of(f.plot, f.ax, 70.0); // clear of the marker's own column
|
||||
const f32 row = row_of(f.plot, f.ay, 40.0);
|
||||
|
||||
// Hover first: what the renderer highlights and what a press grabs are one answer.
|
||||
hover(f.controller, column, row);
|
||||
PLOT_CHECK(f.controller.hovered_cursor() == ruler);
|
||||
PLOT_CHECK(f.plot.hot_handles().cursor == ruler);
|
||||
|
||||
press(f.controller, column, row);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveCursor);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == ruler);
|
||||
PLOT_CHECK(f.plot.hot_handles().grabbed_cursor == ruler);
|
||||
|
||||
// Up is more y: the ruler follows the pointer's row through its own y axis.
|
||||
move_to(f.controller, column, row - 60.0f);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(ruler).y, data_at_row(f.plot, f.ay, row - 60.0f), 1e-9);
|
||||
PLOT_CHECK(f.plot.cursor(ruler).y > 40.0);
|
||||
// The coordinate the ruler does not own, and the marker that owns it, are both untouched.
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(ruler).x, 0.0, 1e-12);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(marker).x, 50.0, 1e-12);
|
||||
// The gesture belonged to the ruler, not to the view: no range moved.
|
||||
PLOT_CHECK_NEAR(f.plot.x_range(f.ax).min, 0.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.x_range(f.ax).span(), 100.0, 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.y_range(f.ay).min, 0.0, 1e-9);
|
||||
|
||||
release(f.controller, column, row - 60.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);
|
||||
// And it stays where the drag left it.
|
||||
PLOT_CHECK(f.plot.cursor(ruler).y > 40.0);
|
||||
}
|
||||
|
||||
/// Nearest-first resolution *across* orientations: a pointer within reach of both a marker and a
|
||||
/// ruler takes the nearer of the two, so neither widens the other's target -- which is what lets
|
||||
/// one hit test serve two instruments. Each press below is 1 px from its winner and 4 px from the
|
||||
/// other, both inside the 5 logical px grab radius. What the winner's drag writes is its own
|
||||
/// coordinate and nothing else.
|
||||
PLOT_TEST(vertical_and_horizontal_cursors_do_not_confuse_each_other) {
|
||||
Fixture f;
|
||||
f.ay = f.plot.add_y_axis({"v", {0.0, 100.0}, false, plot::AxisPlace::Left, false});
|
||||
const u32 marker = f.plot.add_cursor(50.0, plot::Color::hex(0x00FF00));
|
||||
const u32 ruler = f.plot.add_h_ruler(40.0, plot::Color::hex(0x35D0A8));
|
||||
f.plot.layout();
|
||||
const f32 marker_x = screen_x_of(f.plot, f.ax, 50.0);
|
||||
const f32 ruler_y = row_of(f.plot, f.ay, 40.0);
|
||||
const f32 near = 1.0f;
|
||||
const f32 far = 4.0f;
|
||||
|
||||
// Nearer the marker: the point is on both, and the marker is what hovers and what a press
|
||||
// grabs -- the ruler's y does not move with the drag that follows.
|
||||
const f32 marker_side_x = marker_x + near;
|
||||
const f32 ruler_side_y = ruler_y + far;
|
||||
hover(f.controller, marker_side_x, ruler_side_y);
|
||||
PLOT_CHECK(f.controller.hovered_cursor() == marker);
|
||||
press(f.controller, marker_side_x, ruler_side_y);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveCursor);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == marker);
|
||||
move_to(f.controller, marker_side_x + 40.0f, ruler_side_y);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(marker).x, data_at(f.plot, f.ax, marker_side_x + 40.0f), 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(ruler).y, 40.0, 1e-12);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(ruler).x, 0.0, 1e-12);
|
||||
release(f.controller, marker_side_x + 40.0f, ruler_side_y);
|
||||
|
||||
// The symmetric case: the marker put back under the pointer, and the press 1 px from the ruler
|
||||
// and 4 px from the marker. The ruler takes it, and its y alone moves.
|
||||
f.plot.set_cursor_x(marker, data_at(f.plot, f.ax, marker_x));
|
||||
const f32 near_ruler_x = marker_x + far;
|
||||
const f32 near_ruler_y = ruler_y - near;
|
||||
hover(f.controller, near_ruler_x, near_ruler_y);
|
||||
PLOT_CHECK(f.controller.hovered_cursor() == ruler);
|
||||
press(f.controller, near_ruler_x, near_ruler_y);
|
||||
PLOT_CHECK(f.controller.drag_mode() == plot::input::DragMode::MoveCursor);
|
||||
PLOT_CHECK(f.controller.grabbed_cursor() == ruler);
|
||||
const f64 marker_before = f.plot.cursor(marker).x;
|
||||
move_to(f.controller, near_ruler_x, near_ruler_y + 30.0f);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(ruler).y, data_at_row(f.plot, f.ay, near_ruler_y + 30.0f), 1e-9);
|
||||
PLOT_CHECK_NEAR(f.plot.cursor(marker).x, marker_before, 1e-12);
|
||||
release(f.controller, near_ruler_x, near_ruler_y + 30.0f);
|
||||
PLOT_CHECK(f.plot.cursor(ruler).y < 40.0);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
Reference in new issue
Block a user