feat(plot)!: harden pipeline, XY series, dash/markers, touch, packaging
Breaking: Series::gather emits x relative to x0() and takes a GatherMode; consumers reconstruct absolute x in f64 before mapping (series.hpp documents the contract). Bugs, each pinned by a fail-before regression test: - render: f32 staging quantised epoch-time x into 128 px columns; every data-space draw now stages to_space(x) - to_space(origin) with the origin folded into the offset uniform in f64 (epoch/zero-origin twins rasterise identically) - ticks: major test was ULP-fragile and blanked epoch axes; majors are an integer-index test and are never truncated by the minor cap - gather: LOD envelope emitted buckets straddling [begin,end); ceil/floor bucket edges + raw lead/trail spans - live follow: log x axes held a linear span and zoomed every frame; span and offset now live in axis space (plot + both figure link-group paths) - input: a zero-notch wheel disarmed auto_scale on every axis; dy==0 pans x, factor == 1 / inf refused in wheel and drag zoom - render: the single text flush painted legend names over the crosshair readout panel; two batches, under flushed before the panel - data: NaN dropped from min/max unless it seeded the fold; non-finite samples are now excluded from every aggregate and from cached bounds Robustness: viewport normalisation (no clamp UB), non-finite range refusal, pyramid level cap at 57 (no shift/div UB through public gather), rounded ring capacity documented + pre-sized, GL blend func and UNPACK_ALIGNMENT save/restore, Renderer::release_gl() teardown, u64 buffer epochs, annotation captions clamped, button-aware GLFW row capture, cached figure row rects (no per-event layout), ruler-pair measurement refused, span-nonfinite guards in axis_ends. Features: XY series (sorted x, binary-search neighbours/snap, LOD over sample index); StreamSeries::freeze(); ArraySeries::push(); per-trace dash and markers (scatter at width 0) with auto or literal stride; GatherMode Envelope/Nearest/Mean via bucket_sum/sumsq; Style::line_aa_px; per-glyph text colour (readout values in trace colour); wheel-dx x pan; arrow-key pan; Escape cancels a drag and restores captured ranges; modifier-masked box zoom (Shift both, Ctrl x, Alt y); double-click reset via PointerEvent::time_s; ImGui key repeat unified; pinch gestures on Qt and GTK; pixelDelta wheel path and DevicePixelRatioChange handling; GNUInstallDirs, SOVERSION, SameMinorVersion, generated plot/version.hpp, relocatable plot.pc, licence install, FILES_MATCHING, GitHub Actions CI (debug/coverage/asan). Suite 283 -> 350 cases, 0 failed; coverage 93.9% (3730/3971) over 24 files, floor 80/90 held; ASAN preset suite green; all four demos run headless (the GTK scripted-resize check needs a window manager and fails identically on the parent commit); readme_main.cpp committed and compiles with the README command; pkg-config and find_package consumption verified from a relocated install prefix.
This commit is contained in:
1 parent
f132a5ec92
commit
38ef459897
77 files changed
+5905
-688
No files matched your search
@@ -32,10 +32,12 @@ typedef struct _GtkGLArea GtkGLArea;
|
||||
typedef struct _GtkGesture GtkGesture;
|
||||
typedef struct _GtkGestureClick GtkGestureClick;
|
||||
typedef struct _GtkGestureDrag GtkGestureDrag;
|
||||
typedef struct _GtkGestureZoom GtkGestureZoom;
|
||||
typedef struct _GtkEventControllerScroll GtkEventControllerScroll;
|
||||
typedef struct _GtkEventControllerKey GtkEventControllerKey;
|
||||
typedef struct _GtkEventControllerMotion GtkEventControllerMotion;
|
||||
typedef struct _GdkGLContext GdkGLContext;
|
||||
typedef struct _GdkEventSequence GdkEventSequence;
|
||||
|
||||
namespace plot::gtk4 {
|
||||
|
||||
@@ -134,6 +136,10 @@ using FrameFn = std::function<void(Plot&, f64 dt)>;
|
||||
/// release, with the button and the modifier state, exactly as the Qt
|
||||
/// widget's `mousePressEvent`/`mouseReleaseEvent` do. It is also what begins
|
||||
/// a cursor move: a ctrl+press on a cursor opens `DragMode::MoveCursor` there.
|
||||
/// * `GtkGestureZoom` supplies the two-finger pinch: each `scale-changed` is
|
||||
/// folded by `ZoomAnchor` (`gtk_input.hpp`) into one `GestureEvent` -- this
|
||||
/// frame's zoom about the fingers' centre, and the centre's movement, which is
|
||||
/// the two-finger pan a trackpad produces alongside it.
|
||||
/// * `GtkEventControllerMotion` supplies hover (the cursor hit test reads the
|
||||
/// last position even with no button down). Its positions are forwarded with
|
||||
/// `down == false` on purpose: a motion event that claimed a button was down
|
||||
@@ -271,9 +277,16 @@ class PlotArea {
|
||||
void refresh_viewport() noexcept;
|
||||
|
||||
/// One pointer event, from whichever gesture saw it first. Owns the
|
||||
/// press/release bookkeeping the two gestures share.
|
||||
void pointer_press(input::Button button, u32 mods, double x, double y) noexcept;
|
||||
void pointer_release(Vec2 position) noexcept;
|
||||
/// press/release bookkeeping the two gestures share. `time_ms` is the event's
|
||||
/// own GDK stamp, in milliseconds, forwarded as the `PointerEvent::time_s` the
|
||||
/// double-click detector differences (see `pointer_time_s()`).
|
||||
void pointer_press(input::Button button, u32 mods, double x, double y,
|
||||
u32 time_ms) noexcept;
|
||||
void pointer_release(Vec2 position, u32 time_ms) noexcept;
|
||||
/// The gesture event's stamp, in the seconds `PointerEvent::time_s` speaks: GDK
|
||||
/// counts milliseconds since an arbitrary epoch, and only differences are read,
|
||||
/// so the epoch does not matter -- the unit does.
|
||||
static f64 pointer_time_s(u32 time_ms) noexcept { return static_cast<f64>(time_ms) * 1.0e-3; }
|
||||
/// Translates the gesture-reported position/delta in *logical* widget pixels
|
||||
/// into device pixels. The one place that mapping exists.
|
||||
Vec2 to_device(double x, double y) const noexcept;
|
||||
@@ -289,6 +302,15 @@ class PlotArea {
|
||||
static void on_click_released(GtkGestureClick* gesture, int n_press, double x, double y,
|
||||
void* self) noexcept;
|
||||
static void on_click_stopped(GtkGestureClick* gesture, void* self) noexcept;
|
||||
/// The pinch: `begin` anchors the folding, `scale-changed` folds one cumulative
|
||||
/// scale into an incremental `GestureEvent`, and `end` closes it. `begin`/`end`
|
||||
/// carry the sequence, which a one-active-pinch adapter never needs.
|
||||
static void on_zoom_begin(GtkGestureZoom* gesture, GdkEventSequence* sequence,
|
||||
void* self) noexcept;
|
||||
static void on_zoom_scale_changed(GtkGestureZoom* gesture, double scale,
|
||||
void* self) noexcept;
|
||||
static void on_zoom_end(GtkGestureZoom* gesture, GdkEventSequence* sequence,
|
||||
void* self) noexcept;
|
||||
static void on_drag_begin(GtkGestureDrag* gesture, double start_x, double start_y,
|
||||
void* self) noexcept;
|
||||
static void on_drag_update(GtkGestureDrag* gesture, double offset_x, double offset_y,
|
||||
@@ -324,6 +346,9 @@ class PlotArea {
|
||||
/// cached rather than re-read per event, so a `drag-update` reports the movement
|
||||
/// since the previous one, and `have` is whether a drag is recognised.
|
||||
DragAnchor drag_;
|
||||
/// The pinch gesture's folding state (see `ZoomAnchor`): one per area, alive
|
||||
/// between `::begin` and `::end` of the one zoom the gesture recognises.
|
||||
ZoomAnchor zoom_;
|
||||
/// The last pointer position seen, in device pixels: the wheel's anchor when a
|
||||
/// scroll event carries no position of its own.
|
||||
Vec2 pointer_{};
|
||||
|
||||
Reference in new issue
Block a user