feat(gtk4): GtkGLArea-based plot area and demo

This commit is contained in:
Martino Ferrari committed 2026-09-28 02:09:31 +02:00
1 parent 3c0e7763ab
commit 36cd3421f3
10 files changed
+1748 -2

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
#pragma once
#include <plot/core/types.hpp>
#include <plot/input/events.hpp>
#include <optional>
namespace plot::gtk4 {
/// GDK's scroll unit (`GdkScrollUnit`), mirrored here so this header -- and a
/// test that drives it -- needs no GTK include directory. `gtk_plot_area.cpp`
/// static-asserts that the two agree with GDK's own enumerators, so a GDK that
/// renumbered them would fail to compile rather than silently mis-scale a zoom.
enum class ScrollUnit {
Wheel = 0, ///< the delta is already in wheel detents, one per notch
Surface = 1, ///< the delta is in surface logical pixels
};
/// Surface logical pixels per wheel notch, for a `ScrollUnit::Surface` delta.
///
/// GDK reports a smooth device's scroll in surface pixels, the controller takes
/// notches, and passing a pixel delta through would make a touchpad's zoom
/// unusably sensitive -- a flick is hundreds of pixels, and the controller's one
/// notch is ~14% of the span. 120 pixels per notch is the model Wayland's
/// `wl_pointer.axis_value120` introduced ("the value 120 corresponds to one
/// physical scroll click/detent"), and it is the same 120-per-notch scale a Qt
/// `QWheelEvent::angleDelta()` uses (eighths of a degree, 120 of them to a
/// notch), so a smooth device zooms at the detent rate its discrete siblings do
/// rather than an order of magnitude faster. A delta already reported in wheel
/// units is never divided by this.
inline constexpr f64 kSurfacePixelsPerNotch = 120.0;
/// GTK's `GdkModifierType` bits as the controller's `input::Mod` mask.
///
/// A mask, not a reinterpretation: GDK's state carries the *mouse button* bits
/// as well (`GDK_BUTTON1_MASK` is `1 << 8`, and a release reports the button that
/// produced it), so the four modifiers the controller names are the only ones
/// that survive. Without the mask a right-drag would arrive at the controller
/// with an extra `0x400` bit that no other adapter's event carries.
inline u32 mods_from(u32 gdk_state) noexcept {
// The GDK bit values are repeated rather than included: this header names no
// GDK type, and the constants are fixed by GDK's public ABI. `gtk_plot_area.cpp`
// static-asserts them against GDK's own enumerators.
constexpr u32 kGdkShift = 1u << 0;
constexpr u32 kGdkControl = 1u << 2;
constexpr u32 kGdkAlt = 1u << 3;
constexpr u32 kGdkSuper = 1u << 26;
constexpr u32 kGdkMeta = 1u << 28; // maps to Command on macOS, Super here
u32 out = 0;
if ((gdk_state & kGdkShift) != 0u) out |= static_cast<u32>(input::Mod::Shift);
if ((gdk_state & kGdkControl) != 0u) out |= static_cast<u32>(input::Mod::Ctrl);
if ((gdk_state & kGdkAlt) != 0u) out |= static_cast<u32>(input::Mod::Alt);
if ((gdk_state & kGdkSuper) != 0u || (gdk_state & kGdkMeta) != 0u) {
out |= static_cast<u32>(input::Mod::Super);
}
return out;
}
/// The button one of GTK's gestures reports, as the controller's enum.
///
/// Buttons 1, 2 and 3 are the only ones GTK names (left, middle, right) and the
/// only ones the controller has a gesture for. This adapter listens with
/// `gtk_gesture_single_set_button(gesture, 0)`, which GTK documents as "any
/// button", so a press from button 8 or 9 (the navigation buttons) reaches the
/// handler too: `nullopt` is returned for it, and the event is dropped before it
/// can begin a pan.
inline std::optional<input::Button> button_from(u32 gdk_button) noexcept {
switch (gdk_button) {
case 1u: return input::Button::Left;
case 2u: return input::Button::Middle;
case 3u: return input::Button::Right;
default: return std::nullopt;
}
}
/// A GDK scroll delta in wheel notches, positive = zoom in.
///
/// Two conversions in one place, the same two every adapter has to make:
///
/// * the *unit* -- a `ScrollUnit::Surface` delta is scaled by
/// `1 / kSurfacePixelsPerNotch` (see there); a `ScrollUnit::Wheel` delta is
/// already one unit per detent;
/// * the *sign* -- GDK's positive `dy` is a scroll south, the wheel pushed away
/// from the user, while the controller's positive amount zooms in, so the
/// value is negated. A wheel notch up arrives as `dy == -1.0` and leaves here
/// as `+1.0`.
///
/// A non-finite delta is returned unchanged: `Controller::on_wheel` refuses it as
/// a strict no-op, which is the one place that decision belongs.
inline f64 scroll_notches(f64 dy, ScrollUnit unit) noexcept {
if (unit == ScrollUnit::Surface) return -dy / kSurfacePixelsPerNotch;
return -dy;
}
} // namespace plot::gtk4