#pragma once #include #include #include 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(input::Mod::Shift); if ((gdk_state & kGdkControl) != 0u) out |= static_cast(input::Mod::Ctrl); if ((gdk_state & kGdkAlt) != 0u) out |= static_cast(input::Mod::Alt); if ((gdk_state & kGdkSuper) != 0u || (gdk_state & kGdkMeta) != 0u) { out |= static_cast(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 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