Skip to content
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5d76338
Initial plan
Copilot Feb 26, 2026
2494ea8
Add touch/pointer event support to Blazor SKCanvasView
Copilot Feb 26, 2026
b202acb
Add GC.SuppressFinalize in DisposeAsync per review feedback
Copilot Feb 26, 2026
731c882
Remove accidentally committed nuget.exe and add .nuget/ to gitignore
Copilot Feb 26, 2026
9ac847b
Add touch/pointer event support to Blazor SKGLView
Copilot Feb 26, 2026
11f458f
Move touch event types to SkiaSharp.Views.Shared for MAUI/Blazor sync
Copilot Feb 26, 2026
a678a30
Make touch callback synchronous and fix TS formatting
Copilot Feb 26, 2026
e2462c8
Refactor touch interop to match existing Blazor interop patterns
Copilot Feb 26, 2026
3ea113c
Always register touch interop with JS-side enabled toggle; update Bla…
Copilot Feb 27, 2026
f89b93c
Toggle touch via Start/Stop instead of enabled flag; defer until Enab…
Copilot Feb 27, 2026
f451965
Update base href in index.html
mattleibow Feb 27, 2026
5c46f47
Fix JSObject disposal and implement Handled feedback loop
mattleibow Feb 27, 2026
0e0be19
Fix wheel stopPropagation, deltaY=0 edge case, and double-start guard
mattleibow Feb 27, 2026
6b18d05
Normalize wheel delta to v120 standard (120 = one mouse notch)
mattleibow Feb 27, 2026
5b4095f
Revamp Blazor sample app to showcase library features
mattleibow Feb 28, 2026
a501ad8
Revamp Blazor sample app to showcase library features
mattleibow Feb 28, 2026
571c060
Move inline styles to scoped .razor.css files
mattleibow Feb 28, 2026
bd4755c
Move @code to code-behind files and simplify demos
mattleibow Feb 28, 2026
9bd00da
Move code back inline into .razor files
mattleibow Feb 28, 2026
3d3e663
Use raw string literals for SkSL via code-behind files
mattleibow Feb 28, 2026
e697e8e
Move code back inline with @"" verbatim strings in .razor files
mattleibow Feb 28, 2026
f9d0e4d
Align GPU and Drawing pages with Home.razor code style
mattleibow Feb 28, 2026
526bc69
colors
mattleibow Feb 28, 2026
4c959be
Delete SkiaSharpSample.sln
mattleibow Feb 28, 2026
9aba0c0
Guard SKTouchEventArgs with #if __MAUI__ || __BLAZOR__
mattleibow Feb 28, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,4 @@ fastlane/screenshots

# Docs worktree (docs branch)
.docs
.nuget/
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
position: relative;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}

main {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}

.sidebar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> SkiaSharp
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="raster">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Raster Canvas
<NavLink class="nav-link" href="gpu">
<span class="bi bi-gpu-card-nav-menu" aria-hidden="true"></span> GPU
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="gpu">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> GPU Canvas
<NavLink class="nav-link" href="drawing">
<span class="bi bi-pencil-nav-menu" aria-hidden="true"></span> Drawing
</NavLink>
</div>
</nav>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}

.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
.bi-gpu-card-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-gpu-card' viewBox='0 0 16 16'%3E%3Cpath d='M4 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0m7.5-1.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3'/%3E%3Cpath d='M0 1.5A.5.5 0 0 1 .5 1h1a.5.5 0 0 1 .5.5V4h13.5a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5H2v2.5a.5.5 0 0 1-1 0V2H.5a.5.5 0 0 1-.5-.5m5.5 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M9 8a2.5 2.5 0 1 0 5 0 2.5 2.5 0 0 0-5 0'/%3E%3Cpath d='M3 12.5h3.5v1a.5.5 0 0 1-.5.5H3.5a.5.5 0 0 1-.5-.5zm4 0h4v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}

.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
.bi-pencil-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-pencil-fill' viewBox='0 0 16 16'%3E%3Cpath d='M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.5.5 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11z'/%3E%3C/svg%3E");
}

.nav-item {
Expand Down
158 changes: 158 additions & 0 deletions samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Drawing.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
@page "/drawing"

@* Drawing Canvas Demo
Demonstrates SKCanvasView with touch/pointer events, wheel delta for
brush size control, and basic stroke rendering.
Shows EnableTouchEvents, Touch callback, SKTouchEventArgs, and
manual Invalidate() for on-demand rendering (no render loop). *@

<h1>Drawing Canvas</h1>

<p>
Draw with touch or mouse. Use the scroll wheel to change brush size.
</p>

<div class="canvas-container border rounded p-2">

<SKCanvasView @ref="skiaView"
OnPaintSurface="OnPaintSurface" IgnorePixelScaling="true"
Touch="OnTouch" EnableTouchEvents="true" />

<div class="overlay-info">
Brush: @(brushSize.ToString("F2"))px
</div>

<div class="overlay-toolbar">
@foreach (var (name, color) in colors)
{
<div class="color-swatch @(currentColor == color ? "selected" : "")"
style="background:@name"
@onclick="() => SetColor(color)" />
}
<button class="btn btn-outline-light btn-sm" @onclick="ClearCanvas">Clear</button>
</div>

</div>

@code {

SKCanvasView skiaView = null!;
float brushSize = 25f;
SKColor currentColor = SKColors.Black;
List<DrawingStroke> strokes = [];
DrawingStroke? currentStroke = null;
SKPoint lastPoint;

// The paint that is reused for drawing strokes.
readonly SKPaint strokePaint = new()
{
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeCap = SKStrokeCap.Round,
StrokeJoin = SKStrokeJoin.Round,
};

// The available brush colors with their CSS names.
static readonly (string Name, SKColor Color)[] colors =
{
("black", SKColors.Black),
("red", SKColors.Red),
("blue", SKColors.Blue),
("green", SKColors.Green),
("orange", SKColors.Orange),
("purple", SKColors.Purple),
};

void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;

// Clear to white before drawing the strokes.
canvas.Clear(SKColors.White);

// Draw completed strokes.
foreach (var stroke in strokes)
{
strokePaint.Color = stroke.Color;
strokePaint.StrokeWidth = stroke.StrokeWidth;
canvas.DrawPath(stroke.Path, strokePaint);
}

// Draw in-progress stroke.
if (currentStroke is not null)
{
strokePaint.Color = currentStroke.Color;
strokePaint.StrokeWidth = currentStroke.StrokeWidth;
canvas.DrawPath(currentStroke.Path, strokePaint);
}

// Brush size indicator at last pointer position.
strokePaint.Color = SKColors.Gray;
strokePaint.StrokeWidth = 1;
canvas.DrawCircle(lastPoint, brushSize / 2f, strokePaint);
}

void OnTouch(SKTouchEventArgs e)
{
lastPoint = e.Location;

switch (e.ActionType)
{
case SKTouchAction.Pressed:
currentStroke = new DrawingStroke(new(), currentColor, brushSize);
currentStroke.Path.MoveTo(e.Location);
e.Handled = true;
break;

case SKTouchAction.Moved:
if (currentStroke is not null)
{
currentStroke.Path.LineTo(e.Location);
e.Handled = true;
}
break;

case SKTouchAction.Released:
if (currentStroke is not null)
{
strokes.Add(currentStroke);
currentStroke = null;
}
e.Handled = true;
break;

case SKTouchAction.WheelChanged:
brushSize += e.WheelDelta / 120f;
brushSize = Math.Clamp(brushSize, 1f, 50f);
e.Handled = true;
break;
}

skiaView.Invalidate();
StateHasChanged();
}

void SetColor(SKColor color)
{
currentColor = color;
skiaView.Invalidate();
StateHasChanged();
}

void ClearCanvas()
{
foreach (var stroke in strokes)
{
stroke.Path.Dispose();
}
strokes.Clear();
currentStroke?.Path.Dispose();
currentStroke = null;
skiaView.Invalidate();
StateHasChanged();
}

// Represents a single drawing stroke with its path, color, and width.
// Path is owned — callers must dispose when removing strokes.
record DrawingStroke(SKPath Path, SKColor Color, float StrokeWidth = 25);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.canvas-container {
position: relative;
}

.overlay-info {
position: absolute;
top: 12px;
left: 50%;
transform: translateX(-50%);
z-index: 1;
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(0, 0, 0, 0.4);
border-radius: 8px;
padding: 6px 16px;
backdrop-filter: blur(4px);
color: white;
font-size: 0.85rem;
font-weight: 500;
}

.overlay-toolbar {
position: absolute;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
align-items: center;
z-index: 1;
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(0, 0, 0, 0.4);
border-radius: 8px;
padding: 8px 16px;
backdrop-filter: blur(4px);
}

.color-swatch {
width: 26px;
height: 26px;
border-radius: 50%;
cursor: pointer;
border: 2px solid transparent;
}

.color-swatch.selected {
border-color: white;
}
Loading