feat(input): a cursor can be unselectable, for a host's readout marks
A host that draws the result of a computation as a cursor -- a ruler at a level it measured, a marker at a time it found -- must not let the user drag it: the value belongs to the measurement, not to the pointer. Parking it outside the axis and re-placing it every frame does not achieve that, because a drag is held for the gesture's life and the marker follows the pointer between frames, so the readout *looks* draggable however often it is put back. `Cursor::selectable` defaults true; the controller's hit test skips an unselectable cursor, so it is neither hovered nor grabbed and its hit radius goes to whatever is behind it. Set through `Plot::set_cursor_selectable()`. Tested by `an_unselectable_cursor_is_neither_hovered_nor_grabbed` in tests/test_controller.cpp.
This commit is contained in:
1 parent
125cc1f64a
commit
211c5b97bc
5 files changed
+50
-1
No files matched your search
@@ -86,6 +86,12 @@ struct Cursor {
|
||||
u32 axis_x = 0; ///< the x axis `x` is in. Dead for `Horizontal`.
|
||||
u32 axis_y = 0; ///< the y axis `y` is in. Dead for `Vertical`.
|
||||
bool visible = true;
|
||||
/// Whether the pointer may hit this cursor at all: `false` for a *readout* -- a marker a
|
||||
/// host keeps to show something it computed, which the user must not be able to drag. The
|
||||
/// controller's `cursor_at()` skips it, so it is neither hovered nor grabbed, and the
|
||||
/// hit radius it would have taken is left to whatever is behind it. Drawn exactly as a
|
||||
/// visible one is: this is about the gesture, not the ink.
|
||||
bool selectable = true;
|
||||
|
||||
/// True when this is a horizontal ruler (spans the axes' width, reads its `y`). The one
|
||||
/// place the orientation becomes a branch, so every consumer takes the same decision and
|
||||
|
||||
@@ -484,6 +484,10 @@ class Plot {
|
||||
/// be told apart from its neighbours, and its colour is how it says which readout it
|
||||
/// belongs to. An id that names no cursor is ignored, like every other setter here.
|
||||
void set_cursor_color(u32 id, Color color);
|
||||
/// Makes a cursor unselectable (see `Cursor::selectable`): the pointer cannot hover or
|
||||
/// grab it, which is what a host wants for a marker it draws as a *readout*. An id that
|
||||
/// names no cursor is ignored, like every other setter here.
|
||||
void set_cursor_selectable(u32 id, bool selectable);
|
||||
|
||||
/// The measurement for `id`. Re-evaluated only when a cursor moved, when the
|
||||
/// measure was added, or when the series underneath it changed: a different
|
||||
|
||||
@@ -925,7 +925,9 @@ u64 Controller::cursor_at(f32 px, f32 py) const {
|
||||
f32 best_d = 0.0f;
|
||||
for (u32 i = 0; i < plot_->cursor_count(); ++i) {
|
||||
const Cursor& cursor = plot_->cursor(i);
|
||||
if (!cursor.visible) continue;
|
||||
// A readout is drawn but not offered to the pointer: skipping it here is what makes it
|
||||
// undraggable, rather than fighting the drag a frame at a time.
|
||||
if (!cursor.visible || !cursor.selectable) continue;
|
||||
// One hit test for both orientations, because a cursor *is* one line across the axes:
|
||||
// what the pointer has to be near is that line's own coordinate -- the column of a
|
||||
// vertical marker, the row of a horizontal ruler -- measured with the same radius
|
||||
|
||||
@@ -311,6 +311,11 @@ void Plot::set_cursor_color(u32 id, Color color) {
|
||||
cursors_[id].color = color;
|
||||
}
|
||||
|
||||
void Plot::set_cursor_selectable(u32 id, bool selectable) {
|
||||
if (id >= cursors_.size()) return;
|
||||
cursors_[id].selectable = selectable;
|
||||
}
|
||||
|
||||
void Plot::set_annotation(u32 id, const Annotation& annotation) {
|
||||
if (id >= annotations_.size()) return;
|
||||
annotations_[id] = annotation;
|
||||
|
||||
@@ -2309,3 +2309,35 @@ PLOT_TEST(double_click_needs_both_timestamps_and_the_window) {
|
||||
f.controller.on_pointer({410.0f, 200.0f, 0, 0, plot::input::Button::Left, 0, false, false,
|
||||
true, 20.1});
|
||||
}
|
||||
|
||||
// A readout cursor is drawn but not offered to the pointer: the host that keeps it is showing
|
||||
// a computed value, and a drag on it would be a drag on a number rather than on an instrument.
|
||||
// The hit test is where that is enforced -- re-placing the marker every frame would only fight
|
||||
// the drag, which is the shape this replaces.
|
||||
PLOT_TEST(an_unselectable_cursor_is_neither_hovered_nor_grabbed) {
|
||||
plot::Plot p;
|
||||
p.set_viewport({0, 0, 800, 400});
|
||||
const u32 ax = p.add_x_axis({"t", {0.0, 100.0}, false, plot::AxisPlace::Bottom, false});
|
||||
const u32 readout = p.add_cursor(50.0, plot::Color::hex(0x00FF00));
|
||||
p.set_cursor_selectable(readout, false);
|
||||
PLOT_CHECK(p.cursor(readout).visible); // drawn all the same
|
||||
PLOT_CHECK(!p.cursor(readout).selectable);
|
||||
// A normal cursor right beside it stays grabbable, so the flag is a property of one cursor
|
||||
// and not a switch on the plot.
|
||||
const u32 normal = p.add_cursor(50.0, plot::Color::hex(0xFF0000));
|
||||
|
||||
plot::input::Controller c(p);
|
||||
c.set_viewport({0, 0, 800, 400});
|
||||
const f32 sx = screen_x_of(p, ax, 50.0);
|
||||
c.on_pointer({sx, 200, 0, 0, plot::input::Button::Left, 0, false, false, false});
|
||||
PLOT_CHECK(c.hovered_cursor() == normal); // the readout is not in the running
|
||||
|
||||
// Press and move: the pointer grabbed the *normal* cursor (the two are at the same x, so
|
||||
// the readout would otherwise be the nearer hit), and the readout's own x never moves.
|
||||
const f64 before = p.cursor(readout).x;
|
||||
c.on_pointer({sx, 200, 0, 0, plot::input::Button::Left, 0, true, true, false});
|
||||
PLOT_CHECK(c.grabbed_cursor() == normal);
|
||||
c.on_pointer({sx + 40, 200, 40, 0, plot::input::Button::Left, 0, true, false, false});
|
||||
PLOT_CHECK_NEAR(p.cursor(readout).x, before, 1e-12);
|
||||
PLOT_CHECK(c.grabbed_cursor() == normal);
|
||||
}
|
||||
Reference in new issue
Block a user