The repository had no licence at all, so every source was all-rights-reserved by default. Add the verbatim MIT text at LICENSE (Copyright (c) 2026 Martino Ferrari) and a two-line SPDX header to each of the 113 first-party sources -- 48 include/plot, 26 src, 10 adapters, 5 examples, 21 tests, 3 tools -- so the grant travels with each file rather than relying on the root document alone. Scripts keep their shebang on line 1; the SPDX lines follow it. Genuinely vendored code is untouched: third_party/stb_truetype.h carries no header of ours. src/render/embedded_mono_font.cpp is generated, so tools/fonts/gen_atlas.py emit() now writes the two lines into its header comment and the file was regenerated -- gen_atlas.py --check passes and the regeneration adds only those comment lines to the previously committed file. README's provenance section now states MIT and links LICENSE. No code, test or public API change.
60 lines
2.8 KiB
C++
60 lines
2.8 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
|
|
|
// The Qt adapter's pointer translation, driven directly. The widget's cached
|
|
// pointer anchor is a plain value with no toolkit dependency (see
|
|
// adapters/qt6/qt_pointer.hpp) precisely so the one thing a screenshot cannot show
|
|
// -- which pixel space a drag delta is in -- is testable without a QApplication, a
|
|
// window or a display.
|
|
#include "harness.hpp"
|
|
|
|
#include <qt6/qt_pointer.hpp>
|
|
|
|
#include <cmath>
|
|
|
|
// The defect this pins: the anchor used to be cached in *device* pixels while the
|
|
// next event's delta was a raw difference of device positions, so a DPR change
|
|
// between two move events -- the window dragged onto a monitor with a different
|
|
// scale factor mid-drag -- mixed two pixel spaces. At 1.0 -> 1.25 a logical
|
|
// position of 500 produced a spurious 125 device-pixel delta, which
|
|
// `Controller::pan` applies as a fraction of the range span and which also clears
|
|
// auto scale: a scale change mid-drag would move the plot by 125 pixels' worth of
|
|
// data and silently disarm a live axis.
|
|
PLOT_TEST(pointer_anchor_survives_a_dpr_change_between_move_events) {
|
|
plot::qt::PointerAnchor anchor;
|
|
|
|
// The press caches a logical position while the window is on the 1.0 display.
|
|
anchor.set({500.0f, 300.0f});
|
|
|
|
// The next move arrives after the window moved to the 1.25 display, at the same
|
|
// logical position: the pointer has not moved, so the delta must be zero -- not
|
|
// 500 * 1.25 - 500 * 1.0 = 125 device pixels, which is what comparing two device
|
|
// positions across the scale change reported.
|
|
const plot::Vec2 still = anchor.device_delta({500.0f, 300.0f}, 1.25);
|
|
PLOT_CHECK_NEAR(still.x, 0.0, 1e-6);
|
|
PLOT_CHECK_NEAR(still.y, 0.0, 1e-6);
|
|
|
|
// A real movement is the logical delta scaled by the ratio in effect now, in both
|
|
// directions.
|
|
const plot::Vec2 moved = anchor.device_delta({503.0f, 295.0f}, 1.25);
|
|
PLOT_CHECK_NEAR(moved.x, 3.0 * 1.25, 1e-6);
|
|
PLOT_CHECK_NEAR(moved.y, -5.0 * 1.25, 1e-6);
|
|
|
|
// And the anchor follows the sample it just reported: the following move is
|
|
// measured from there, at whatever ratio that event carries.
|
|
anchor.set({503.0f, 295.0f});
|
|
const plot::Vec2 unchanged = anchor.device_delta({503.0f, 295.0f}, 1.0);
|
|
PLOT_CHECK_NEAR(unchanged.x, 0.0, 1e-6);
|
|
PLOT_CHECK_NEAR(unchanged.y, 0.0, 1e-6);
|
|
}
|
|
|
|
// The first sample of a gesture has no anchor to measure from, so its delta is
|
|
// zero however far the position is from the origin: the first motion of a drag
|
|
// must never include the jump to where the drag began.
|
|
PLOT_TEST(pointer_anchor_reports_no_movement_before_its_first_sample) {
|
|
plot::qt::PointerAnchor anchor;
|
|
const plot::Vec2 delta = anchor.device_delta({500.0f, 300.0f}, 1.25);
|
|
PLOT_CHECK_NEAR(delta.x, 0.0, 1e-9);
|
|
PLOT_CHECK_NEAR(delta.y, 0.0, 1e-9);
|
|
}
|