Skip to content

Add native iPhone and Android WebGPU hosts and mobile WinUI support#29

Merged
wieslawsoltes merged 15 commits into
mainfrom
agent/native-iphone-webgpu-mobile
Jul 22, 2026
Merged

Add native iPhone and Android WebGPU hosts and mobile WinUI support#29
wieslawsoltes merged 15 commits into
mainfrom
agent/native-iphone-webgpu-mobile

Conversation

@wieslawsoltes

@wieslawsoltes wieslawsoltes commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

This change adds experimental native iPhone and Android hosts for ProGPU and runs the existing shared WinUI gallery directly on WebGPU over Metal and Vulkan. It also completes the mobile-facing WinUI surface needed by those hosts: lifecycle events, safe-area and keyboard occlusion, native text input and file/folder pickers, precise trackpad input, indirect-pointer drag/drop, wrapped variable-height DataGrid rows, reusable variable-size virtualization, and WinUI-shaped Page, Frame, panel, navigation, display, back-navigation, and deferral APIs.

Both mobile routes reuse the same retained renderer, scene compiler, controls, shaders, and sample pages as desktop and browser. iOS presents to a physical-pixel CAMetalLayer through a statically linked, Metal-only wgpu-native XCFramework. Android presents to a physical-pixel ANativeWindow/SurfaceView through the same Silk.NET WebGPU ABI and wgpu-native's Vulkan backend. Neither route uses a web view, JavaScript command serialization, MAUI, Uno, a second canvas, or a readback path.

Why

ProGPU previously had desktop and browser hosts but no direct native Apple mobile surface. The browser architecture already provided a useful platform-neutral IWindowHost and external-renderer boundary, but its WebAssembly/JavaScript command transport is browser-specific and would add unnecessary CPU work on iPhone.

The shared control layer also assumed desktop-style window geometry, fixed-height virtualization, discrete mouse-wheel input, and hardware-keyboard text events. Those assumptions caused unsafe-area overlap, missing software-keyboard behavior, nonfunctional simulator trackpad gestures and mouse drag/drop, unusable pickers, wrapped DataGrid text overlapping adjacent rows, and an incorrectly stretched Compute FX offscreen texture on compact layouts.

Architecture and implementation

Native WebGPU/Metal backend

  • Add a ProGPU.iOS UIKit host and a thin ProGPU.Samples.iOS executable that launches the existing ProGPU.Samples.App.
  • Render directly into one CAMetalLayer sized from logical bounds multiplied by the screen's native scale.
  • Drive presentation with a lifecycle-aware CADisplayLink at the screen's maximum supported refresh range.
  • Configure three outstanding drawables, framebuffer-only presentation, high-performance adapter selection, direct surface recovery, and terminal device-loss reporting.
  • Resolve Silk.NET WebGPU entry points from a statically linked native archive on iOS while retaining normal dynamic loading on desktop.
  • Pin the wgpu-native C ABI consumed by Silk.NET.WebGPU 2.23.0 and provide a reproducible script that builds device and Apple Silicon simulator XCFramework slices with only WGSL and Metal enabled.
  • Replace callback closures used by native adapter/device/map operations with AOT-safe unmanaged callbacks or Silk task wrappers.
  • Package the static resolver support through ProGPU.Backend build-transitive assets and provide the unreachable link stub required by browser AOT.

Native WebGPU/Vulkan Android backend

  • Add a dependency-light .NET Android host and shared-gallery executable using SurfaceHolder, ANativeWindow, Choreographer, and direct wgpu-native Vulkan presentation.
  • Keep adapter/device/queue, pipelines, atlases, and retained scene resources alive across ordinary activity pause/resume and detach only the presentation surface when Android destroys it.
  • Enforce Vulkan adapter selection, physical framebuffer sizing, one-time density conversion, edge-to-edge insets, system back dispatch, clipboard, Storage Access Framework documents/folders, and a transactional native IME bridge.
  • Route multi-touch, stylus, mouse buttons/hover, precise two-axis wheel and API 34 gesture-distance axes, batched history, pinch, capture, and native ClipData drag/drop through the portable input contracts.
  • Prefer non-sRGB Bgra8Unorm/Rgba8Unorm presentation because ProGPU writes encoded sRGB channel values directly; this prevents double transfer-function encoding. Disable Android's full-SurfaceView default focus overlay so indirect input cannot wash out the presented frame.
  • Pin and reproducibly build ABI-compatible ARM64/x64 libwgpu_native.so artifacts with WGSL input, deterministic manifests/checksums, 16 KiB ELF/APK alignment, and no vendored upstream source.

WinUI controls and panels

  • Add WinUI-shaped Page, reflection-free/AOT-safe Frame navigation, navigation events/options/cache modes, RelativePanel, VariableSizedWrapGrid, Viewbox, ItemsStackPanel, ItemsWrapGrid, group-header placement, display information, lifecycle deferrals, and system back navigation.
  • Move Stretch to the official Microsoft.UI.Xaml.Media namespace and preserve compatibility in image, rich text, markdown, and shared samples.
  • Extend UniformVirtualizingGridPanel with variable item extents, prefix-sum lookup, viewport anchoring, bounded realization/recycling, horizontal/vertical layouts, and visible/cache index reporting.

WinUI-shaped mobile window APIs

  • Make Window derive from DependencyObject and align activation, close, size, visibility, title-bar, bounds, launch, and application-start contracts with the official WinUI API shapes.
  • Add host-neutral lifecycle notifications shared by browser and iOS.
  • Implement the official Windows.UI.ViewManagement.InputPane surface with occlusion, showing/hiding events, focused-element reporting, and platform show/hide callbacks.
  • Add ProGPU's small cross-platform WindowInsets companion for safe-area, keyboard occlusion, and visible bounds because official WinUI does not expose a portable safe-area API.
  • Keep the compositor backdrop full-surface while arranging ordinary content inside safe areas and docked keyboards; report floating keyboards without shrinking the entire window.
  • Reveal focused editors through the nearest ScrollViewer or DataGrid when the keyboard covers them.

Mobile text, pointer, gestures, and drag/drop

  • Add a UIKit UITextInput bridge with exact replacement ranges, selection synchronization, marked-text composition, cancellation, deletion, return handling, input scopes, password mode, capitalization, spell checking, autocorrection, dictation edits, and hardware/software keyboard support.
  • Route clipboard text through UIPasteboard.
  • Preserve precise two-axis logical-pixel scroll deltas from browser and UIKit trackpads instead of quantizing them into desktop wheel steps.
  • Route UIKit scroll, hover, pinch, touch, Pencil, and indirect mouse events through typed ProGPU pointer input with stable pointer IDs, pressure, contact geometry, button masks, and timestamps.
  • Separate continuous trackpad scrolling from discrete mouse-wheel detents, preserve logical-pixel trackpad motion, and transport pinch scale without step quantization through 120 * ln(scale).
  • Implement the current Microsoft.UI.Input.GestureRecognizer contract with official settings, pointer metadata and transforms, tap/double-tap/right-tap/hold, mouse and pen drag, touch cross-slide, translation rails, scale, rotation, pivot rotation, wheel manipulation, completion, and configurable automatic/manual inertia.
  • Align XAML gesture arguments with the official Microsoft.UI.Input value/device types and emit timed routed inertial deltas with interruption plus desired deceleration/displacement/angle/expansion behavior.
  • Support trackpad pan/zoom in the DXF viewer.
  • Preserve indirect mouse contact ownership through capture and drag/drop so simulator mouse drags complete against the original pointer.

Native storage pickers

  • Implement file-open, file-save, and folder selection with UIKit document pickers.
  • Translate WinUI extension filters into native content types.
  • Copy opened documents into the app container for safe portable reads.
  • Retain security-scoped save/folder URLs and coordinate writes with NSFileCoordinator.
  • Make picker completion and cancellation deterministic and covered by platform-service tests.

Shared variable-size virtualization and DataGrid wrapping

  • Add VariableSizeIndex, a reusable Fenwick prefix-sum geometry index shared by VirtualizingStackPanel and DataGrid.
  • Provide O(log N) measurement updates, item-to-offset lookup, and offset-to-item lookup with O(N) retained numeric state.
  • Preserve measured rows across collection growth/removal, invalidate measurements on width changes, keep a viewport anchor while estimates are replaced, and realize bounded viewport overscan.
  • Support variable sizing with ItemHeight/RowHeight = float.NaN plus explicit estimated sizes.
  • Add WinUI-shaped DataGrid cell/column TextWrapping settings and measure wrapped cells into each realized row's actual height.
  • Keep recycling and visible rebind operations bounded to realized containers.

Shared samples and regression fixes

  • Reuse the same gallery app and page implementations across desktop, browser, and iPhone.
  • Use the dependency-light rich-document registry on browser/iOS so OpenXML is unreachable and removed by full trimming while desktop retains DOCX.
  • Make picker actions responsive on compact widths.
  • Size the Compute FX offscreen scene from the actual arranged preview instead of subtracting a desktop pane width from the iPhone window. This fixes the stretched/broken background geometry while retaining the existing textures and compute pipelines.
  • Document the native architecture, ABI pin, build/run workflow, simulator isolation, physical-device expectations, API decisions, performance constraints, and primary-source research in docs/ios.md.

Performance characteristics

  • iPhone uses direct managed-to-native WebGPU dispatch and direct WebGPU-to-Metal presentation; there is no browser transport, intermediate canvas, or frame readback.
  • The existing retained scene, GPU batching, atlases, pipeline caches, and CPU shaping/layout reuse remain unchanged across hosts.
  • Physical drawable sizing prevents OS-level texture stretching and preserves Retina output.
  • Compute FX preview-size selection is O(1) with no per-frame discovery or allocation.
  • Variable-size virtualization uses O(log N) prefix-sum operations and bounded realization rather than scanning all rows.
  • Android uses direct managed-to-native WebGPU calls and Vulkan presentation with no JNI call per draw, intermediate bitmap, Java canvas, or second renderer. API 34 touchpad distances remain display-pixel precise; normalized wheel axes are converted through ViewConfiguration exactly once.
  • The fully trimmed Release simulator build sustains the simulator's 60 FPS display cadence on the Compute FX and 10,000-row DataGrid pages; physical-device frame-time, thermal, memory, and power measurements remain required before declaring the host production-ready.

Root causes addressed

Area Root cause Resolution
iPhone rendering No native surface/lifecycle path and no static WebGPU symbol resolution Add UIKit/CAMetalLayer host, static resolver, and Metal-only XCFramework
Safe areas and keyboard Window content assumed the entire logical client rectangle Add safe-area/input-pane geometry and inset-aware arrangement
Software keyboard Key events could not express UIKit replacement, selection, or marked text Add exact typed text-edit operations and a UITextInput bridge
Trackpad scrolling Continuous deltas were normalized/quantized and UIKit indirect events were not admitted correctly Preserve precise 2D deltas and use dedicated indirect gesture recognizers
WinUI gestures Gesture contracts were partial, used duplicate XAML value types, and skipped routed inertial deltas Match current Microsoft.UI.Input contracts and run typed gesture/routed inertia state machines
Mouse drag/drop Indirect input did not retain a stable mouse pointer/contact owner Route button masks and stable pointer IDs through capture and drag completion
Pickers No native iOS implementation of the WinUI storage service Add document/folder pickers with sandbox and security-scope handling
Wrapped DataGrid cells Rows used one fixed height regardless of wrapped content Measure realized cell content and index variable row extents
Compute FX on compact layouts Offscreen width always subtracted a desktop navigation pane Use the arranged preview size and compact overlay fallback
Android dark colors First advertised surface format was sRGB while compositor output was already encoded Prefer non-sRGB presentation and lock selection with tests
Android indirect-input washout Native default focus drawable covered the entire edge-to-edge SurfaceView Disable the platform overlay and retain ProGPU focus visuals
Android wheel jumps Host converted wheel units to pixels but controls multiplied by row height again Mark converted logical-pixel deltas precise and consume them once
Missing mobile controls Shared UI lacked core WinUI Page/Frame/panel/navigation API shapes Add typed AOT-safe controls, panels, events, and layout tests

Validation

  • dotnet test src/ProGPU.Tests/ProGPU.Tests.csproj --no-build --no-restore
    • 2,252 passed, 0 failed.
  • dotnet test src/ProGPU.Tests.Headless/ProGPU.Tests.Headless.csproj --no-build --no-restore
    • 195 passed, 0 failed.
  • dotnet build src/ProGPU.Samples.iOS/ProGPU.Samples.iOS.csproj -c Release -r iossimulator-arm64 --no-restore
    • succeeded with 0 errors and 6 known warnings: the command-line RID notice, two existing dependency-property trim-analysis warnings, and trim summaries from netDxf plus two Silk.NET support assemblies.
  • The fully trimmed/AOT Release app was installed and launched on a dedicated iPhone 17 Pro simulator.
  • Manual native WebGPU/Metal validation covered the gallery shell, safe areas, Compute FX, the 10,000-row virtualized DataGrid, precise trackpad scrolling, DXF pan/zoom, mouse drag/drop, keyboard editing/IME behavior, and native pickers.
  • The standalone Android ARM64 Release APK published with full managed AOT and full trimming, contained only arm64-v8a, passed 16 KiB zip alignment, and packaged the audited wgpu-native digest b8102032e4e6b425327e507d8dfa48f2f2d898ac25524a9b0c57fa116b4109e7.
  • The final AOT APK launched on the dedicated Android 16/API 36 ProGPU_API_36 emulator using Apple M3 Pro -> Vulkan, Rgba8Unorm, and a 1280x2856 physical target at scale 3. The first frame contained 65 draws, 732 vector vertices, and 293 text vertices; the warm gallery HUD reported 56 FPS.
  • Android runtime checks covered dark-palette presentation before/after indirect input, bounded variable-height DataGrid wheel scrolling, native picker UI, and background/resume with the same process and GPU context. Android shell emits no gesture axes for its synthetic touchpad source, so representative real-hardware two-finger/inertia validation remains a release gate.
  • git diff --check origin/main...HEAD passes.
  • Clean-room audit found no copied/ported implementation markers or staged generated native/build artifacts.

Follow-up and limitations

  • Measure cold start, first interaction, sustained scroll percentiles, memory, thermals, power, and image quality on physical iPhone hardware.
  • Add application-level reconstruction after terminal WebGPU device loss; recoverable surface loss already reconfigures in place.
  • Add native external drag/drop, accessibility-tree integration, and multi-scene policy when required by applications.
  • Complete physical Android device validation for touchpad inertia/pinch, stylus tilt, external drag/drop, IME languages, refresh-rate changes, thermals, power, and GPU frame percentiles.
  • Upgrade Silk.NET's generated WebGPU ABI and the pinned wgpu-native commit together; newer native releases are not ABI-compatible drop-in replacements.

Research

The implementation is clean-room work based on public API contracts and primary sources. Detailed adopted/adapted/rejected decisions are recorded in docs/ios.md and docs/android.md, including Apple CAMetalLayer and indirect input, Android ANativeWindow/Vulkan/surface/input/IME/storage contracts, the WebGPU specification, wgpu/wgpu-native, .NET mobile AOT/linking, WinUI public APIs, Skia, Direct2D/Win2D, WebRender, Vello, Parley, HarfBuzz, and WinUI virtualization contracts.

@wieslawsoltes
wieslawsoltes marked this pull request as ready for review July 21, 2026 14:19

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d06f7b7e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

{
_silkWindow.IsVisible = false;
}
NotifyHostVisibilityChanged(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore visibility state when reactivating hidden windows

When a desktop window is hidden, this new notification sets Window.Visible to false, but the Activate() path for an existing _silkWindow only sets IsVisible = true and focuses the native window; it never calls NotifyHostVisibilityChanged(true). Any app that hides and later re-activates a window will therefore keep the new WinUI-shaped Visible property false and miss the corresponding VisibilityChanged(true) event even though the native window is shown again.

Useful? React with 👍 / 👎.

Comment on lines +1271 to 1273
textAlignment: wrapping != TextWrapping.NoWrap && FlowDirection == FlowDirection.RightToLeft
? ProGPU.Text.TextAlignment.Right
: ProGPU.Text.TextAlignment.Left);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve RTL alignment for no-wrap cells

With the default CellTextWrapping = NoWrap, this condition now forces every RTL DataGrid cell to TextAlignment.Left; before this change, RTL rows were right-aligned regardless of wrapping. In any right-to-left grid that does not opt into wrapping, cell text regresses to the wrong side of the cell, so the no-wrap drawing path needs to keep RTL alignment while avoiding the oversized no-wrap bounds issue.

Useful? React with 👍 / 👎.

@wieslawsoltes wieslawsoltes changed the title Add native iPhone WebGPU host and mobile WinUI support Add native iPhone and Android WebGPU hosts and mobile WinUI support Jul 21, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a9096cfe2e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

{
safeArea = NormalizeInsets(safeArea);
inputPaneOccludedRect = NormalizeOccludedRect(inputPaneOccludedRect);
if (_safeAreaInsets.Equals(safeArea) && _inputPaneOccludedRect.Equals(inputPaneOccludedRect)) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-run input-pane reveal for unchanged occlusion

When the soft keyboard is already visible and focus moves to another text input, the mobile hosts keep reporting the same safe-area and occluded-rect values, so this early return skips the only call to EnsureFocusedElementInView(). A newly focused editor that is under the existing keyboard will therefore remain hidden until the keyboard geometry changes; the unchanged-insets path still needs to attempt the focused-element reveal while the input pane is visible.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01e24c92f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

FindSingleReplacement(_lastText, current, out int start, out int removedLength, out string inserted);
int selectionStart = Math.Max(0, _editText.SelectionStart);
int selectionEnd = Math.Max(selectionStart, _editText.SelectionEnd);
InputSystem.InjectTextReplacement(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle replacement edits in password fields

When a mobile PasswordBox is focused, this non-composition edit path sends TextInputEventKind.ReplaceText for ordinary typing, but PasswordBox.ITextInputClient.OnTextInput only handles InsertText, delete, composition, and paste. Since PasswordBox.GetTextInputOptions() also exposes an empty native document, every normal Android software-keyboard character is dropped; the same replacement path exists in the iOS bridge, so password entry is unusable on native mobile until PasswordBox handles replacement/selection edits or the bridges special-case password clients.

Useful? React with 👍 / 👎.

Comment on lines +108 to +112
public override void DidBecomeActive(UIScene scene) => _host?.Resume();

public override void WillResignActive(UIScene scene) => _host?.Pause();

public override void DidEnterBackground(UIScene scene) => _host?.Pause();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Forward iOS scene lifecycle to Application

In the iOS scene lifecycle, these callbacks only pause/resume rendering and never call Application.Current.NotifyHostSuspendingAsync, NotifyHostEnteredBackgroundAsync, NotifyHostResuming, or NotifyHostLeavingBackgroundAsync (repo-wide, only the Android host invokes those new hooks). Apps that rely on the WinUI-shaped lifecycle events to save state or release resources therefore receive no Suspending/background/resume notifications when an iPhone app is backgrounded or reactivated.

Useful? React with 👍 / 👎.

}

_controller.View!.LayoutIfNeeded();
MetalViewMetrics metrics = _renderView.Metrics;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Publish iOS display metrics to DisplayInformation

The iOS host reads native view metrics here but never publishes them through DisplayInformation.NotifyHostMetricsChanged (the only repo call is in the Android host). As a result, DisplayInformation.GetForCurrentView() on iPhone keeps the default landscape/96-DPI/zero-pixel values even after activation, so orientation- or DPI-adaptive UI sees incorrect display state on the new native iOS host.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b208cad7e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

removedLength,
selectionStart,
selectionEnd - selectionStart);
_lastText = current;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resync the native editor after rejected edits

When the focused client rejects or truncates this replacement, this still advances _lastText to the native current text. For example, a mobile RichEditBox with MaxLength returns without inserting once the limit is reached, so the hidden Android/iOS editor keeps extra characters; subsequent backspace/delete ranges are then computed past the ProGPU text and get clamped to no-ops until the user deletes the unaccepted native overflow. The bridge needs to resync from the focused client's TextInputOptions after dispatch instead of assuming every replacement was applied verbatim.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab98041204

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

var row = (int)((y - _headerHeight + ScrollOffset) / _rowHeight);
float offset = y - _headerHeight + ScrollOffset;
int row = IsVariableRowHeight
? EnsureRowSizeIndex().GetIndexAtOffset(offset)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject hits beyond the last variable-height row

When RowHeight is NaN, GetIndexAtOffset clamps offsets at or beyond the total size to Count - 1. Since GetRowIndexAt feeds any body y into it and then only checks that the returned row is in range, clicking or hovering the blank area below the final row (for example in a short grid or when scrolled to the bottom) incorrectly targets the last item and can select/edit it. Guard offset >= TotalBodyHeight before the variable-height lookup so empty grid space remains non-interactive.

Useful? React with 👍 / 👎.

checked((ulong)Math.Max(0L, e.EventTime) * 1_000UL),
WheelDeltaX: deltaX,
WheelDeltaY: deltaY,
IsPreciseWheel: precise,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark Android pinch wheel events as precise

For Android 14+ touchpad pinch events that report GesturePinchScaleFactor but no scroll-distance axes, scroll.IsPrecise remains false, so the ctrl-wheel event injected here is not marked precise. Consumers such as DxfCanvasControl.OnPointerWheelChanged only use the exact 120 * ln(scale) zoom path when IsPreciseScrolling is true, so Android pinch zoom falls back to a single stepped mouse-wheel zoom instead of the actual pinch scale. Set precise in the pinch branch when the delta is rewritten.

Useful? React with 👍 / 👎.

_editText.ImeOptions = MapImeAction(options.EnterKeyHint, options.AcceptsReturn);
_editText.SetSingleLine(!options.AcceptsReturn);
PositionNativeEditor(options);
SynchronizeNativeDocument(options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resync native text state after focused caret moves

Syncing the hidden native editor only from the focus-change path leaves its text/selection stale when the user taps or drags to move the caret inside an already-focused TextBox/RichEditBox (the focus setter returns without raising FocusChanged). The next IME edit is then diffed against the old native selection and inserted at the previous caret position, so ordinary mobile editing after repositioning the caret corrupts placement. The bridge needs an explicit resync from the focused ITextInputClient when the focused editor's selection/text changes, not just when focus changes.

Useful? React with 👍 / 👎.

@wieslawsoltes
wieslawsoltes merged commit 36451dc into main Jul 22, 2026
5 checks passed
@wieslawsoltes
wieslawsoltes deleted the agent/native-iphone-webgpu-mobile branch July 22, 2026 05:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant