Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Published releases are `2.5.x` (latest tag `v2.5.0`). Do not treat `IDEAL_API.md
- **KDoc on public API**: `UndocumentedPublicClass` / `UndocumentedPublicFunction` are enforced by detekt (`detekt` is wired into `check` / `preMerge`). Pre-existing gaps are grandfathered in per-module `<module>/detekt-baseline.xml` files — any *new* undocumented public class or function fails the build. Do not regenerate a baseline to silence a new finding; write the KDoc. `UndocumentedPublicProperty` stays off because the generated icon/symbol catalogs (`sf-symbols`, `freedesktop-icons`) would swamp it
- **Logging**: `java.util.logging` is the single facade for every runtime module — no SLF4J dependency forced on consumers, no raw `println` / `System.err` in `src/main`. Logger names must be the fully-qualified class name (or an explicit `dev.nucleusframework.*` string) so the whole framework sits under one JUL namespace. `allowNucleusRuntimeLogging = true` is an opt-in convenience that raises the `dev.nucleusframework` logger to `nucleusLoggingLevel` and attaches a colored console handler; apps that configure JUL themselves (`logging.properties`, `jul-to-slf4j`) leave it `false` and Nucleus never touches the JUL configuration
- `decorated-window-tao` is the only window backend (no AWT, native event-loop-driven, true Windows fullscreen, GraalVM native-image first-class). The AWT-based backends (`decorated-window-awt` / `-jbr` / `-jni`), `NucleusBackend`, `LocalNucleusBackend`, the `backend =` parameter of `nucleusApplication`, and `NucleusWindowUnsafe.awtWindow` / `awtDialog` were all removed in 2.6. Compose Desktop's AWT `Window` / `Dialog` / `Tray` are unsupported — use `DecoratedWindow`, `HostedWindow` / `HostedDialog`, and an AWT-free tray
- **macOS trackpad on Tao** (#652–#654): scroll deltas are AWT-shaped (`preciseWheelRotation`, no display scale). Trackpad gestures reach Compose as `PanStart` / `PanMove` / `PanEnd` (`panOffset` = AWT delta × 10 dp), wheel notches as `Scroll`; foundation's `Modifier.scrollable` handles both. Custom handlers that only listen for `PointerEventType.Scroll` must also handle Pan, or the app can set `-Dnucleus.tao.trackpadPanEvents=false` to get AWT-style `Scroll` for everything. Everything scroll-related enters the scene through `TaoSceneScrollRouter` (window + NSPanel popups); the phase wire (Rust `SCROLL_GESTURE_*`, `popup_panel.m`, `TaoScrollGesturePhase`) is guarded by `TaoScrollWireDriftTest`
- **macOS trackpad on Tao** (#652–#654, #660): scroll deltas are AWT-shaped (`preciseWheelRotation`, no display scale). Trackpad two-finger swipe reaches Compose as `PanStart` / `PanMove` / `PanEnd` (`panOffset` = AWT delta × 10 dp), wheel notches as `Scroll`; foundation's `Modifier.scrollable` handles both. Custom handlers that only listen for `PointerEventType.Scroll` must also handle Pan, or the app can set `-Dnucleus.tao.trackpadPanEvents=false` to get AWT-style `Scroll` for everything. Everything scroll-related enters the scene through `TaoSceneScrollRouter` (window + NSPanel popups); the phase wire (Rust `SCROLL_GESTURE_*`, `popup_panel.m`, `TaoScrollGesturePhase`) is guarded by `TaoScrollWireDriftTest`. Platform-recognized pinch is `ScaleStart` / `ScaleChange` / `ScaleEnd` (`scaleFactor` = per-event ratio) via `dispatchTrackpadScale` — not two synthetic Touch contacts; `Modifier.transformable` and MapLibre consume that path, while `detectTransformGestures` still only sees two-finger rotate. Linux/Windows pinch (GDK / Ctrl+wheel) uses the same Scale events.
- macOS Liquid Glass enabled by default via `macOsSdkVersion = "26.0"` (vtool SDK patching)
- The HotSpot GC is selected type-safely with `application { garbageCollector = GarbageCollector.Z }` (unset = JVM ergonomics). The flags are prepended to the launcher `.cfg` java-options and to the `run` task — before `jvmArgs`, so an explicit `-XX:+Use…GC` there still wins — and the AOT training run inherits them from the `.cfg`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,8 @@ internal fun ApplicationScope.openDecoratedWindow(

// Trackpad pinch / rotate / smart-magnify, intercepted before AppKit
// dispatches them down the responder chain (Tao 0.35 doesn't surface
// these events). Synthesised as two-finger Touch pointers in the host
// so cross-platform `detectTransformGestures` reacts uniformly.
// these events). Pinch is forwarded as Compose Scale events (#660);
// rotation still synthesises two-finger Touch pointers.
window.onTrackpadGesture { kind, phase, x, y, value ->
exceptionHandler.catchExceptions {
if (enabled) host.onTrackpadGesture(kind, phase, x, y, value)
Expand Down Expand Up @@ -1105,9 +1105,8 @@ private fun ApplicationScope.openDecoratedWindowWindows(

// Trackpad pinch-to-zoom. Windows delivers a precision-touchpad pinch (and
// a real Ctrl+wheel) as a Ctrl-flagged WM_MOUSEWHEEL; the Tao patch routes
// those to the magnify hook instead of a scroll, and the host synthesises a
// two-finger Touch pinch so cross-platform `detectTransformGestures` zooms
// uniformly — same model as macOS.
// those to the magnify hook instead of a scroll, and the host forwards
// Compose Scale events (#660) — same model as macOS.
window.onTrackpadGesture { kind, phase, x, y, value ->
exceptionHandler.catchExceptions {
if (enabled) host.onTrackpadGesture(kind, phase, x, y, value)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package dev.nucleusframework.window.tao.event

import androidx.compose.ui.InternalComposeUiApi
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.scene.ComposeScene

/**
* Feeds one step of a platform-recognized pinch into the scene as Compose's
* `ScaleStart` / `ScaleChange` / `ScaleEnd` (#660). [scaleFactor] is a
* multiplicative per-event ratio (`1f` = no change, `> 1f` zoom in, `< 1f`
* zoom out) — the same shape as `NSEvent.magnification` after `1 + delta`,
* and as GDK's per-event pinch ratio. Foundation's `transformable` and
* apps that listen for `PointerEventType.Scale*` consume it directly, so
* unlike the previous two-finger Touch synthesis there is no second pass
* through touch slop, span thresholds or release momentum.
*/
@OptIn(InternalComposeUiApi::class)
internal fun ComposeScene.dispatchTrackpadScale(
x: Float,
y: Float,
type: PointerEventType,
scaleFactor: Float,
keyboardModifiers: PointerKeyboardModifiers = PointerKeyboardModifiers(),
) {
sendPointerEvent(
eventType = type,
position = Offset(x, y),
type = PointerType.Mouse,
keyboardModifiers = keyboardModifiers,
scaleGestureFactor = scaleFactor,
)
}

/**
* Open/move/close a Compose scale gesture from a platform pinch stream
* (`TaoTrackpadPhase` on macOS/Linux, a debounced tick stream on
* Windows / Linux Ctrl+wheel). UI thread only.
*/
internal class TaoTrackpadScaleSession(
private val send: (type: PointerEventType, scaleFactor: Float) -> Unit,
) {
var active: Boolean = false
private set

/** Opens the scale gesture if it is not already open. */
fun start() {
if (active) return
active = true
send(PointerEventType.ScaleStart, 1f)
}

/**
* Opens the gesture if needed and reports a multiplicative [scaleFactor].
* A `1f` factor is not a move (Began / Ended ticks, a zero wheel delta).
*/
fun change(scaleFactor: Float) {
if (scaleFactor == 1f) return
start()
send(PointerEventType.ScaleChange, scaleFactor)
}

/**
* [delta] is `NSEvent.magnification` / GDK's equivalent: the next factor
* is `1 + delta`, floored so a collapse cannot invert the scale.
*/
fun magnifyBy(delta: Float) {
change((1f + delta).coerceAtLeast(MIN_GESTURE_SCALE))
}

/** One-shot smart-magnify: a discrete zoom step, then the gesture closes. */
fun smartMagnify() {
start()
change(SMART_MAGNIFY_FACTOR)
end()
}

fun end() {
if (!active) return
active = false
send(PointerEventType.ScaleEnd, 1f)
}

internal companion object {
const val SMART_MAGNIFY_FACTOR: Float = 1.5f
const val MIN_GESTURE_SCALE: Float = 0.05f
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import kotlin.math.pow

/**
* Maps a Ctrl+wheel / precision-touchpad wheel delta to a multiplicative zoom step.
* Shared by the Windows and Linux hosts, which both synthesise a magnify gesture from
* Ctrl+wheel so it zooms (never scrolls) — the AWT backend has no pinch-zoom, so this
* gives Windows/Linux the same behaviour.
* Shared by the Windows and Linux hosts, which both turn Ctrl+wheel into a
* Compose scale gesture so it zooms (never scrolls) — the AWT backend has no
* pinch-zoom, so this gives Windows/Linux the same behaviour.
*/
internal object TaoWheelPinchZoom {
private const val WHEEL_DELTAS_PER_DOUBLING: Float = 12f
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import dev.nucleusframework.core.runtime.NativeLibraryLoader
* single `Release` carrying the final position.
* - **Trackpad gesture**: matches [NativeTaoBridge.EventCallback.onTrackpadGesture]
* exactly — same kind / phase / fixed-point scaling. The Rust side has
* already converted GDK's absolute pinch scale into per-event ratio
* deltas and GDK's radian angle deltas into degrees, so the JVM-side
* synth math is platform-independent.
* already converted GDK's absolute pinch scale into a per-event ratio
* (forwarded as Compose Scale events, #660) and GDK's radian angle
* deltas into degrees, so the JVM-side math is platform-independent.
*
* Coordinates passed to [Callback.onTouchEvent] are physical pixels in the
* GtkWindow's bin-child coordinate space, encoded as fixed-point ×1024
Expand Down Expand Up @@ -71,8 +71,8 @@ internal object NativeTaoLinuxTouchBridge {
/**
* Trackpad pinch / rotate. Same wire format as
* [NativeTaoBridge.EventCallback.onTrackpadGesture] so the JVM-side
* synth math (`TaoComposeSceneHost.onTrackpadGesture`) is reused
* verbatim across macOS and Linux. Wayland-only on Linux.
* scale / rotate dispatch is reused across macOS and Linux.
* Wayland-only on Linux.
*/
@Suppress("LongParameterList", "FunctionParameterNaming")
fun onTrackpadGesture(
Expand Down
Loading
Loading