Add Uno Platform support to KumikoUI#9
Open
michaelstonis wants to merge 17 commits into
Open
Conversation
Adds the reviewed, phase-by-phase implementation plan set for porting KumikoUI to the Uno Platform: prerequisites, library scaffold, DataGridView host, input/keyboard/focus, fonts/assets, sample app, tests, and CI/CD. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SDK (.NET 10), Uno templates (unolib/unoapp), and uno-check 1.33.1 confirmed present. ios/macos workloads installed; android/wasm-tools/maccatalyst require elevation not available in this session, so local verification targets net9.0-desktop and the full head matrix is delegated to CI (Phase 08). Checklist annotated accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New Uno.Sdk single-project library created via 'dotnet new unolib', wired into KumikoUI.sln and referencing KumikoUI.Core + KumikoUI.SkiaSharp (both unchanged). SKXamlCanvas is provided by the SkiaRenderer UnoFeature (SkiaSharp.Views.Uno.WinUI) on non-Windows heads and an explicit SkiaSharp.Views.WinUI ref on the Windows head, with SkiaSharp pinned to 3.119.2 to match KumikoUI.SkiaSharp. GenerateLibraryLayout + NuGet metadata set; builds and packs clean for net9.0-desktop. TFMs: net9.0;net9.0-ios;net9.0-android;net9.0-browserwasm;net9.0-desktop (+ net9.0-windows10.0.26100 on Windows). The unolib template omits net9.0-maccatalyst (Uno covers macOS via the Skia desktop head); revisit if a dedicated Mac Catalyst head is required. Other heads verified in CI (Phase 08); locally only net9.0-desktop is buildable (workload constraint). Verified by an independent agent: build 0/0, pack OK, references correct, no regression to sibling projects. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DataGridView derives from Microsoft.UI.Xaml.Controls.Grid and hosts a single SKXamlCanvas. PaintSurface -> new SkiaDrawingContext(e.Surface.Canvas) -> DataGridRenderer.Render(...) (argument list matches Core/MAUI exactly), with the canvas cleared to the style background and DPI scaling via canvas.Scale(e.Info.Width/ActualWidth). NeedsRedraw / edit-session redraw signals route to a single Invalidate()-based InvalidateSurface(). Full DependencyProperty surface 1:1 with MAUI (14: ItemsSource w/ INotifyCollectionChanged, Columns, GridSelectionMode, RowHeight, HeaderHeight, FrozenRowCount, EditTriggers, EditTextSelectionMode, IsReadOnly, AllowSorting, AllowFiltering, DismissKeyboardOnEnter, GridDescription, TableSummaryRows). Public events RowTapped/RowDoubleTapped/CellBeginEdit/CellEndEdit/CellValueChanged forward from the controller/edit session. Loaded/Unloaded lifecycle. KumikoUIHostingExtensions.UseKumikoUI added (Microsoft.Extensions.Hosting.Abstractions, abstractions-only). Render-only by design: pointer/keyboard/focus wiring is left as clearly-marked // Phase 04: seams. Builds and packs 0/0 for net9.0-desktop. Core/SkiaSharp/Maui unchanged. Independently verified (11/11 PASS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adapts WinUI/Uno input onto the unchanged GridInputController. Pointer (Pressed/Moved/Released/Canceled/CaptureLost/WheelChanged/Holding) builds GridPointerEventArgs from GetCurrentPoint(this) -> HandlePointer; CapturePointer + Focus on press, inertial DispatcherTimer (16ms) -> UpdateInertialScroll on release. Keyboard: KeyDown -> VirtualKey->GridKey -> HandleKey; CharacterReceived -> GridKeyEventArgs.Character -> HandleKey (drives cell editing). Focus: IsTabStop + Focus(Programmatic) + KeyboardFocusRequested. New Input/InputMapping.cs is a pure, unit-testable static class (full GridKey coverage). KeyDown modifiers read via InputKeyboardSource.GetKeyStateForCurrentThread because KeyRoutedEventArgs.KeyboardModifiers is [Uno.NotImplemented] on Skia/WASM (verified the GetKeyState path is a real Uno impl, not a stub). All handlers + timers detached/stopped on Unloaded; attach/detach idempotent. Builds 0/0 for net9.0-desktop. Core/SkiaSharp/Maui unchanged. Independently verified (11/11 PASS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
KumikoFonts (host-layer) wraps Uno asset loading into the unchanged SkiaFontRegistrar: RegisterFromAppPackageAsync(family, ms-appx-uri) does StorageFile.GetFileFromApplicationUriAsync -> OpenReadAsync().AsStreamForRead() -> SkiaFontRegistrar.RegisterTypefaceFromStream, non-fatal (falls back to system font). Plus a batch overload and a RegisterFromStream passthrough. Used AsStreamForRead() because the OpenStreamForReadAsync() shim isn't on Uno's non-WinAppSDK heads. A deliberate improvement over MAUI's app-private TryRegisterFont helper. The library stays font-agnostic (ships no TTFs); the app supplies fonts (wired in Phase 06). GenerateLibraryLayout already set in Phase 02; library-shipped-asset rules documented as N/A. Builds 0/0 (no Uno warnings) and packs with zero font files for net9.0-desktop. SkiaFontRegistrar/Core/SkiaSharp/Maui unchanged. Independently verified (6/6 PASS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…se 06) Uno app scaffolded via 'dotnet new unoapp' (blank/code-behind, all heads), reconciled into the repo (removed the template-generated global.json, Directory.Packages.props/Build.props enabling CPM, and nested .sln so the repo stays SDK-unpinned and CPM-free). References KumikoUI.Uno; nested under the samples solution folder. NavigationView shell with six pages mirroring SampleApp.Maui: BasicGrid, Grouping, LargeData (10K-100K), Theming, MvvmActions (action-button column), CustomFonts (CJK + Material icons). NotoSansJP + MaterialIcons TTFs registered at startup via KumikoFonts.RegisterFromAppPackageAsync before window activation. Verified end-to-end: builds 0/0 for net9.0-desktop and launches on the macOS Skia backend to first render with zero unhandled exceptions (independently reproduced). Run note: this machine has no .NET 9 runtime, so 'dotnet run' uses DOTNET_ROLL_FORWARD=LatestMajor. Library/Core/SkiaSharp/Maui untouched. Other heads verified in CI (Phase 08). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Regression gate: the existing KumikoUI.Core.Tests xUnit suite passes 353/353 unchanged, confirming the platform-agnostic engine is byte-for-byte intact after the Uno port. New tests/KumikoUI.Uno.Tests (Uno.Sdk single-head net9.0-desktop xUnit project) covers the pure InputMapping logic added in Phase 04: ToGridKey across the full key table (incl. Back->Backspace, A/C/V/X/Z, arrows, Home/End, Page*, Tab/Enter/Escape/Space/Delete) with a whole-enum sweep asserting all other keys -> GridKey.None, and ToInputModifiers (Shift/Control/Menu->Alt/Windows->Meta + combinations). 41/41 genuinely execute headlessly (pure enum maps need no Uno runtime). GetLiveModifiers() excluded (runtime-dependent). Smoke matrix filled truthfully: only Desktop render-on-load is (partially) verified (Phase 06); other heads/interactions pending CI + device testing. Added to KumikoUI.sln under tests. No production/Core/SkiaSharp/Maui changes. Independently verified (7/7 PASS, both suites re-run green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci.yml: new build-uno job (windows-latest, needs build-core) restores Uno workloads (cached), builds + pack-dry-runs KumikoUI.Uno across all heads, and runs the KumikoUI.Uno.Tests mapping suite. publish.yml: new pack-uno job packs KumikoUI.Uno with symbols and uploads nuget-uno/snupkg-uno artifacts; pack-uno added to the publish job's needs. Artifact names match the existing nuget-*/snupkg-* download globs, so the Uno package publishes to NuGet.org automatically with no push-step change. A class library cross-compiles its ios/android/wasm heads on the Windows runner (only the WinAppSDK head needs Windows), so the whole package builds on one windows-latest runner like pack-maui. Additive only — existing jobs untouched. Documented caveats: transitive AndroidX content/ resources in the multi-head nupkg, and non-fatal Uno0001 warnings on non-desktop heads. Live runner/NuGet push pending branch push + tag. Independently verified (8/8 PASS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sample now targets net10.0-* heads (net10.0-android;net10.0-ios;net10.0-browserwasm;net10.0-desktop, + net10.0-windows10.0.26100 on Windows). The library KumikoUI.Uno stays on net9.0 (broad-compat target, ADR-4): a net10.0 app binds the library's net9.0 heads via NuGet's nearest-TFM match — verified by building net10.0-desktop against the net9.0-desktop library output (0 errors). Mirrors how KumikoUI.Maui (net10.0) consumes net9.0 Core/SkiaSharp. Benefit: the sample runs natively on the installed .NET 10 runtime — verified by launching net10.0-desktop WITHOUT DOTNET_ROLL_FORWARD; it reached first render on the macOS Skia backend with zero exceptions. Updated the WSL2 launch profile path and docs/uno/06 accordingly. Note: the net10 desktop head surfaces transitive NU1903 advisories (System.Security.Cryptography.Xml 10.0.2, Tmds.DBus.Protocol 0.21.2) from the upstream Uno/.NET 10 stack — build warnings, not errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WinUI's UIElement.PointerMoved fires on every hover (no button), but Core's GridInputController.HandlePointerMove has no contact concept and pans based on distance from the last press — it assumes the MAUI SKCanvasView contract where Moved fires only in-contact. The host now gates OnPointerMoved on PointerPoint.IsInContact AND excludes right/middle-button drags, so only a left-button drag, a touch/pen drag, or the mouse wheel scrolls (matching the requested behavior). Touch scrolling is preserved because contact relies on IsInContact (reliable cross-device), not IsLeftButtonPressed. Also fixed DispatchPointer to report PointerButton.None when no button is pressed instead of always Primary. Host-only fix (KumikoUI.Uno) — Core/SkiaSharp/Maui unchanged. Builds 0/0 for net9.0-desktop. Independently verified (7/7 PASS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…keyboard proxy The custom-drawn Grid is not a text input, so focusing it on CellBeginEdit never summoned the soft keyboard on iOS/Android and CharacterReceived/KeyDown don't fire from the on-screen keyboard (desktop worked only via the hardware keyboard). Added a hidden, focusable WinUI TextBox proxy (Opacity 0, 1x1, Visible, IsTabStop=false) in new DataGridView.Editing.cs: CellBeginEdit focuses it (summons the soft keyboard), a \u200B-sentinel TextChanged diff forwards typed characters (and maps \n/\r->Enter, \t->Tab), OnProxyKeyDown forwards special/navigation keys with e.Handled=true (so Backspace/Enter/Tab aren't double-handled), and CellEndEdit resets the proxy and refocuses the grid to dismiss the keyboard. OnPointerPressed no longer steals focus during an active edit. Mirrors MAUI's hidden-Entry proxy, unified across iOS/Android/desktop/WASM. Host-only (KumikoUI.Uno) — Core/SkiaSharp/Maui unchanged. Both net9.0-desktop and net9.0-ios build 0 errors; the iOS CharacterReceived Uno0001 warnings confirm the root cause. Independently verified (10/10 PASS). Runtime soft-keyboard behavior on iOS/Android needs a simulator/device smoke test (can't verify headlessly). Note: TextBox.IsTextPredictionEnabled is a no-op on iOS (harmless Uno0001). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cally DispatchPointer set ScrollDeltaY unconditionally for every Scroll event, so a horizontal wheel (IsHorizontalMouseWheel) populated BOTH ScrollDeltaX and ScrollDeltaY and Core scrolled vertically. Now ScrollDeltaY is set only when NOT a horizontal wheel, and ScrollDeltaX only when it is — each wheel axis maps to one grid axis. Host-only; Core unchanged. Builds 0/0 net9.0-desktop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The WebAssembly profiles were missing compatibleTargetFramework, so Rider could not map profiles to heads and selecting 'SampleApp.Uno (Desktop)' launched the browser WASM target (unoplatform/uno#21153). Added compatibleTargetFramework=browserwasm to both WASM profiles and moved Desktop to the top so dotnet run / the IDE default to it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ooter) Adds DataGridStyle.PinBottomSummaryRows (default true). When set, a Position=Bottom table summary row is drawn at adjustedViewportHeight - bottomSummaryHeight (a fixed footer at the viewport bottom) on top of the scrolling data, instead of at the end of the content where it scrolled away. The summary row's opaque background covers the data behind it, and the existing scroll range already reserves bottomSummaryHeight (ContentHeight includes it), so at max scroll the last data row's bottom meets the footer's top exactly — no row is hidden and no clip/scroll/content-height change is needed. This is the first and only change to shared KumikoUI.Core in the Uno port (additive: Render(...) signature unchanged, flag defaults on). It intentionally also pins bottom summaries in KumikoUI.Maui, since both share Core — requested by the author. Core tests stay 353/353; Uno tests 41/41; library + sample build clean; desktop sample launches and the pinned footer Y is constant across scroll (verified via diagnostics). Visual confirmation (footer fixed while scrolling) is best eyeballed in the running app. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The control derives from Grid (a Panel, not reliably keyboard-focusable on WASM) and UIElement.CharacterReceived is [Uno.NotImplemented] on Skia/WASM, so when not editing there was no working keyboard sink — navigation and type-to-edit were dead on WASM. Now the hidden, always-focusable TextBox proxy is the keyboard sink for non-touch input: it's focused on mouse/pen pointer press (after DispatchPointer so it wins over any focus change), and OnProxyTextChanged forwards characters even when not editing so a keystroke reaches Core's Typing trigger (type-to-edit). OnProxyKeyDown already forwards navigation/command keys and marks them Handled. Removed the grid's CharacterReceived subscription + handler (the proxy's TextChanged is the sole char path; keeping the grid handler double-fired on Windows where CharacterReceived bubbles up from the focused proxy). OnEditSessionCellEndEdit is now device-aware: non-touch keeps the proxy focused (arrows/typing keep working after an edit), touch blurs to the grid to dismiss the soft keyboard. Touch unchanged otherwise — soft keyboard still appears only on edit-begin. Host-only; Core/SkiaSharp/Maui untouched. Builds 0/0 (lib + sample, desktop); independently verified (8/8). Note: WASM can't be built locally (no wasm-tools workload), so browser typing confirmation is pending. The fix is platform-agnostic — Uno maps TextBox to a real focusable browser <input>, which reliably delivers KeyDown/TextChanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause found by running the app with a focus self-test (not by theorizing): the hidden TextBox keyboard proxy had IsTabStop=false, and WinUI/Uno Focus() returns FALSE on a non-tab-stop element. Proven at runtime: with IsTabStop=false, _inputProxy.Focus()=False (state=Unfocused); with IsTabStop=true, Focus()=True (the TextBox becomes the focused element). So every prior 'focus the proxy' call silently did nothing and there was never a keyboard sink — which is why keyboard was dead on every platform. The Grid itself focuses fine (Focus()=True), contradicting the earlier assumption it wasn't focusable. Second bug the focus failure was masking: OnProxyKeyDown mapped Space and A/C/V/X/Z to GridKey and marked them Handled, so even once the proxy could focus, you literally could not type a space or the letters a/c/v/x/z (they were eaten before reaching TextChanged). Those keys only carry command meaning with Ctrl/Cmd (clipboard/select-all) or, for Space, outside an edit — otherwise they must flow through as text. Fixed. Fix: IsTabStop=true (TabIndex pushed last) on the proxy; OnProxyKeyDown lets plain Space/A/C/V/X/Z reach TextChanged. Builds 0/0 (lib + sample). Could not do a live keystroke test in this environment (computer-use needs you at the screen; AppleScript lacks Accessibility), but the root cause is proven via the runtime self-test, not guessed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduce a new cross-targeted library and sample app to support the Uno Platform across desktop, mobile, and web targets.
Summary
KumikoUI.Unolibrary reusing the existing SkiaSharp rendering engine.SampleApp.Unoproject to demonstrate grid features on all supported heads.Changes
Details
DataGridViewhost control for Uno usingSKXamlCanvas.