Skip to content

Dev/skiafiddle ux improvements#3790

Merged
mattleibow merged 17 commits into
mono:mainfrom
ramezgerges:dev/skiafiddle-ux-improvements
Apr 28, 2026
Merged

Dev/skiafiddle ux improvements#3790
mattleibow merged 17 commits into
mono:mainfrom
ramezgerges:dev/skiafiddle-ux-improvements

Conversation

@ramezgerges

Copy link
Copy Markdown
Contributor

Description of Change

Bugs Fixed

  • Fixes #

API Changes

None.

Behavioral Changes

None.

Required skia PR

None.

PR Checklist

  • Has tests (if omitted, state reason in description)
  • Rebased on top of main at time of PR
  • Merged related skia PRs
  • Changes adhere to coding standard
  • Updated documentation

ramezgerges and others added 7 commits April 28, 2026 13:50
After bumping SkiaSharp to 4.147.0, the generated samples flow
(`dotnet cake --target=samples-generate`) fails restore on
`output/samples/Gallery/Uno/SkiaSharpSample.Uno.csproj` with:

    Error NU1605: Detected package downgrade: SkiaSharp.Skottie from
    4.147.0 to 3.119.2.
      SkiaSharpSample.Uno -> SkiaSharpSample.Shared -> SkiaSharp.Skottie (>= 4.147.0)
      SkiaSharpSample.Uno -> SkiaSharp.Skottie (>= 3.119.2)

`SkiaSharpSample.Shared` brings in Skottie 4.147.0 (its in-tree
ProjectReference is rewritten to a PackageReference 4.147.0 by
samples-generate). The Uno gallery csproj has no direct Skottie ref;
the only "near" Skottie in its closure comes from Uno.WinUI.Lottie at
3.119.2, which NuGet's resolver flags as a downgrade against the
deeper 4.147.0 ask from Shared.

The targets file is the right place to declare the Skottie
ProjectReference (matching the SkiaSharp / HarfBuzzSharp pattern), but
samples-generate strips imports of `samples/_UnoPlatformSamples.targets`
along with the file itself — so a PackageReference there alone would
not propagate to the generated csproj. The direct PackageReference in
the gallery csproj is what actually fixes the generated build:
samples-generate's version-substitution bumps it to whatever's in
VERSIONS.txt, putting the floor at 4.x and shadowing Lottie's
transitive 3.x.

Two changes:
- `samples/_UnoPlatformSamples.targets`: add the in-tree
  `SkiaSharp.Skottie` ProjectReference next to the existing
  SkiaSharp / HarfBuzzSharp ones.
- `samples/Gallery/Uno/SkiaSharpSample.Uno.csproj`: pin Skottie with
  `ExcludeAssets="all"` so the package only constrains the version
  floor; the actual Skottie code keeps coming from Shared (in the
  generated build) or the in-tree project ref via the targets file
  (in the local build). A duplicate PackageReference here AND in the
  targets file trips NU1504 (and Uno.Implicit.Packages chokes on it),
  so the targets file holds only the ProjectReference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shared gallery's FillPathSample built its shapes with mutating
SKPath methods (MoveTo/LineTo/AddRoundRect/AddCircle/Close), all of
which were marked [Obsolete("Use SKPathBuilder instead.")] when
SkiaSharp landed at 4.147.0. SkiaSharpSample.Shared has
TreatWarningsAsErrors=true, so the gallery and SkiaFiddle Uno builds
fail with CS0618 in CI:

    error CS0618: 'SKPath.MoveTo(float, float)' is obsolete: 'Use
    SKPathBuilder instead.'
    error CS0618: 'SKPath.AddRoundRect(SKRoundRect, SKPathDirection)'
    is obsolete: 'Use SKPathBuilder instead.'
    ...

Rewrite the five Create*Path helpers to construct an SKPathBuilder,
configure it via the same MoveTo/LineTo/AddCircle/AddRoundRect/Close
calls (those methods are still supported on the builder), and call
Detach() to materialize the SKPath returned to the caller. The
builder is `using`-disposed; the caller owns the detached path
exactly as before.

FillPathSample is the only sample that still uses the obsolete
mutating SKPath methods directly — every other sample in
samples/Gallery/Shared/Samples/ already uses SKPathBuilder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Uno wasm build was linking three copies of libSkiaSharp.a (in-tree st,
NuGet st, NuGet st,simd), producing duplicate-symbol errors across libpng,
libwebp and friends.

SkiaSharp.NativeAssets.WebAssembly's targets file adds both st and st,simd
variants to the wasm-ld command unconditionally. Listing the package as a
direct PackageReference with ExcludeAssets="all" mutes both — the in-tree
output/native/wasm/libSkiaSharp.a Content glob in IncludeNativeAssets.SkiaSharp.targets
remains the sole source. Same treatment for HarfBuzzSharp.NativeAssets.WebAssembly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The in-tree Uno glob was libSkiaSharp.a/**/*.a, which would link both st
and st,simd if the native build ever produced both. Mirror the Blazor
block's _SkiaSharpNativeBinaryType computation (driven by WasmEnableThreads
and WasmEnableSIMD) and pin the variant directory in the Content glob so
WasmEnableSIMD=false actually selects what the SkiaFiddle README claims it
selects. Same change in IncludeNativeAssets.HarfBuzzSharp.targets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the stacked Setup/Draw editor split with a tabbed pane that
defaults to the Draw tab, since the Draw block is what users edit most.
Both editors stay mounted (visibility-toggled) so Monaco doesn't lose
state when switching.

Add Platforms/WebAssembly/WasmScripts/MonacoFocusGuard.js to stop
Monaco's key/composition events from bubbling up to Uno's document-level
listener — Monaco renders into a native HTML overlay on top of Uno's
Skia canvas, and without the guard typed input could trigger Uno-side
accelerators or focus changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CodeEditor.Text doesn't reliably reflect what the user typed: Monaco's
onDidChangeContent → Accessor.setValue("Text", …) round-trip into managed
code lags behind keystrokes under Uno WASM, so RunAsync was reading the
seeded sample text and Roslyn was happily recompiling the original
snippet. Typing nonsense into the Draw box "compiled" successfully and
the animation never changed.

skiaFiddleGetMonacoValues (in MonacoFocusGuard.js) reads each editor's
live model value via EditorContext, and RunAsync pulls from there with a
.Text fallback for the early-load case. The order is DOM order, which
matches XAML declaration order (Draw first, Setup second).

While here, switch the inactive tab from Visibility.Collapsed to Opacity
+ IsHitTestVisible + IsTabStop. Collapsing skips Monaco's Loaded event,
so a never-shown editor would never initialize Monaco and our JS lookup
would return only one value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Orbits animation is a better landing showcase than the static
gradient, so default the dropdown to it. Picker is selected by name so
list reorders don't break the default.

Drop the header's three-column grid in favor of a HorizontalAlignment
overlay: brand on the left, sample picker centered, no right-column
filler. The two children share the same row but lay out independently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

📦 Try the packages from this PR

Warning

Do not run these scripts without first reviewing the code in this PR.

Step 1 — Download the packages

bash / macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.sh | bash -s -- 3790

PowerShell / Windows:

iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 3790"

Step 2 — Add the local NuGet source

dotnet nuget add source ~/.skiasharp/hives/pr-3790/packages --name skiasharp-pr-3790
More options
Option Description
--successful-only / -SuccessfulOnly Only use successful builds
--force / -Force Overwrite previously downloaded packages
--list / -List List available artifacts without downloading
--build-id ID / -BuildId ID Download from a specific build

Or download manually from Azure Pipelines — look for the nuget artifact on the build for this PR.

Remove the source when you're done:

dotnet nuget remove source skiasharp-pr-3790

ramezgerges and others added 3 commits April 28, 2026 16:58
The stopPropagation guard on .monaco-editor was an attempt to keep typing
inside the editor from leaking into Uno's document-level event routing.
In practice it broke typing entirely — Monaco stopped receiving
keystrokes — so drop it and keep only the skiaFiddleGetMonacoValues JS
helper, which is what actually fixes the recompile bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-add a keyboard guard but at the document level in capture phase,
filtering for events whose target is inside .monaco-editor. This runs
before Uno's own document listener, calls stopImmediatePropagation, and
leaves Monaco's textarea-level listeners untouched (they fire later in
target/bubble). The previous version listened on .monaco-editor itself,
which sat in the wrong phase to interpose on Uno's routing.

Tab strip is now drawn on top of the editor stack in the same Grid cell
instead of sitting in a separate row above it. The editor reserves space
with Margin-top, but its layout box still extends under the tab strip,
so any sub-pixel clip overflow from Monaco's native HTML overlay lands
behind the tab strip rather than next to it. Eliminates the white
rectangle that was poking out where the tab buttons should be.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
collapse the inactive tab

The previous guard called stopImmediatePropagation at document/capture,
which also aborts the dispatch to the target — so Monaco's textarea-level
listeners never fired and Ctrl+C/V, backspace at line boundaries, etc.
silently broke. (Letters and space still made it through because Monaco
relies on `input` events for those, which we weren't intercepting.)

Replace it with a document.addEventListener monkey-patch that wraps any
keyboard listener Uno installs at document scope and makes it skip
events whose target is inside .monaco-editor. The wrapper only short-
circuits Uno's own handler — Monaco's listeners are on the textarea
inside .monaco-editor, not on document, so they're untouched and every
keystroke reaches Monaco normally.

For the white rectangle on the tab strip: switch the inactive editor's
hide mechanism from Opacity=0 / IsHitTestVisible=False to
Visibility=Collapsed. Opacity-based hiding kept the inactive Monaco's
native HTML overlay in the rendering pipeline, where its sub-pixel clip
overflow added on top of the active editor's. Visibility=Collapsed is
display:none — the overlay is dropped from rendering, leaving only the
active editor's clip to deal with (and that one sits behind the tab
strip thanks to the z-stack from the previous commit).

Pre-collapse, snapshot the live editor texts via GetEditorTexts and
re-assign them to the managed Text properties so Monaco can rehydrate
from the cache if it loses state when its host control is hidden.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mattleibow

mattleibow commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

ramezgerges and others added 6 commits April 28, 2026 17:44
Increase the editor stack's top Margin from 34 to 48 so Monaco's native
HTML overlay starts ~14px below the tab strip's bottom edge. The
sub-pixel clip overflow that was bleeding into the tab area now lands
inside the EditorBackground gap below the tab strip instead, leaving
the buttons visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The clip-path Uno generated for Monaco's native overlay in the previous
layout (single-cell Grid with the inner editor Grid using Margin=48 and
the tab strip drawn on top via VerticalAlignment=Top) didn't honor the
inner Grid's margin — it hugged the tab strip's bottom edge regardless,
producing an overlay that punched a hole through Skia's render across
the tab buttons. Switching to a 2-row Grid (tabs in row 0, editors in
row 1) gives the editor a real layout cell with a clean boundary, so
Uno emits a clip-path that actually starts at the row 1 top edge. This
mirrors the structure we had before tabs were introduced (where Uno
generated correct per-editor clip-paths for the two stacked editors).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Uno's WASM Skia compositor appears to punch a transparent hole for each
native HTML overlay sized to the element's bounding box rather than its
clip-path, so when Monaco's box is larger than the clip-path the
unclipped portion of the hole exposes the body's default white
background. Force body and .monaco-editor to the panel color so
whatever leaks through blends in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tabbed UI exposed an Uno WASM Skia clip-path bug — Monaco's native
HTML overlay would punch a hole through Skia across the tab buttons
regardless of margin or row layout, leaving a white rectangle where the
tabs should be. Reverting to the original stacked layout (Setup above
Draw, each in its own row with a header) since that's the structure
Uno generates a correct per-editor clip-path for.

Preserved from the tab-era work:
* Recompile fix — GetEditorTexts pulls live values from Monaco's models
  via skiaFiddleGetMonacoValues, so RunAsync no longer sends the seeded
  sample text to Roslyn even when the Monaco→managed Text round-trip
  lags.
* Keyboard guard — document.addEventListener wrapper makes any keyboard
  listener Uno installs at document scope skip events whose target is
  inside .monaco-editor, stopping 's'/space/etc. from leaking into the
  ComboBox or focused button.
* Default sample — picker defaults to Animated · Orbits.
* Centered sample picker in the page header.

Removed: tab strip XAML, SelectTab / OnDrawTabClicked / OnSetupTabClicked
handlers, the body/Monaco background-paint CSS shim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Orbits is now the default sample, so the "Default" prefix was
misleading. The new name follows the same `<category> · <topic>`
pattern as the other samples (Animated · Orbits, Shader · Plasma, …).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GetEditorTexts mapped values[0] to Draw because the tabbed XAML put
DrawEditor first in DOM order. Restoring the stacked layout flipped
that — SetupEditor is now declared first (top), DrawEditor second
(bottom) — so values[0] is the Setup block now. The previous mapping
was sending the Draw block to Roslyn as Setup (and vice versa), which
made every fresh run fail with class-scope-illegal expressions like
`canvas.Clear(...)` at the start of Setup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

@mattleibow
mattleibow merged commit 84a9b24 into mono:main Apr 28, 2026
6 of 7 checks passed
@ramezgerges
ramezgerges deleted the dev/skiafiddle-ux-improvements branch April 29, 2026 08:05
github-actions Bot added a commit that referenced this pull request Apr 29, 2026
Add docs landing page improvement (#3794), note CI and workflow
automation improvements (#3755, #3787, #3802, #3805, #3808, #3811,
#3812, #3813), and expand highlights paragraph.

Community sample fixes by @ramezgerges (#3785, #3790) were already
present.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants