Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
14 changes: 14 additions & 0 deletions SkiaSharp.Extended.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Extended.UI.Maui.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharpDemo.Blazor", "samples\SkiaSharpDemo.Blazor\SkiaSharpDemo.Blazor.csproj", "{B7E4C45C-5CAB-444E-B2D3-294151544256}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Extended.UI.Blazor", "source\SkiaSharp.Extended.UI.Blazor\SkiaSharp.Extended.UI.Blazor.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Extended.UI.Blazor.Tests", "tests\SkiaSharp.Extended.UI.Blazor.Tests\SkiaSharp.Extended.UI.Blazor.Tests.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -53,6 +57,14 @@ Global
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|Any CPU.Build.0 = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -64,6 +76,8 @@ Global
{2C67033A-2C49-4146-B942-9CDD2E0BA412} = {51B0C2C7-732B-4A5C-A4F2-55655D147866}
{4B4EC78C-33B5-456D-BD7D-4358D16272F4} = {5555F827-12DF-4D15-BF07-3A720FC2EF3F}
{B7E4C45C-5CAB-444E-B2D3-294151544256} = {51B0C2C7-732B-4A5C-A4F2-55655D147866}
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {5DEC7961-7CE3-44D7-A7FC-6185BA2D37FE}
{C3D4E5F6-A7B8-9012-CDEF-123456789012} = {5555F827-12DF-4D15-BF07-3A720FC2EF3F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {08D78153-5DD7-4C52-A348-46AA448B2CFC}
Expand Down
8 changes: 8 additions & 0 deletions samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
<span class="bi bi-image-nav-menu" aria-hidden="true"></span> BlurHash
</NavLink>
</div>

<div class="nav-group-header px-3">EXTENDED UI</div>

<div class="nav-item px-3">
<NavLink class="nav-link" href="touch">
<span class="bi bi-hand-index-nav-menu" aria-hidden="true"></span> Touch
</NavLink>
</div>
</nav>
</div>

Expand Down
4 changes: 4 additions & 0 deletions samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z'/%3E%3Cpath d='M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1h12z'/%3E%3C/svg%3E");
}

.bi-hand-index-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M6.75 1a.75.75 0 0 1 .75.75V8a.5.5 0 0 0 1 0V5.467l.086-.004c.317-.012.637-.008.816.027.134.027.294.096.448.182.077.042.15.147.15.314V8a.5.5 0 1 0 1 0V6.435a4.9 4.9 0 0 1 .106-.01c.316-.024.584-.01.708.04.118.046.3.207.486.43.081.096.15.19.2.259V8.5a.5.5 0 0 0 1 0v-1h.342a1 1 0 0 1 .995 1.1l-.271 2.715a2.5 2.5 0 0 1-.317.991l-1.395 2.442a.5.5 0 0 1-.434.252H6.035a.5.5 0 0 1-.416-.223l-1.433-2.15a1.5 1.5 0 0 1-.243-.666L3.4 8.18a.978.978 0 0 1 .56-1.048 .636.636 0 0 1 .544.025l.082.04c.258.126.458.265.575.37V1.75A.75.75 0 0 1 6.75 1z'/%3E%3C/svg%3E");
}

.nav-group-header {
font-size: 0.7rem;
font-weight: 600;
Expand Down
300 changes: 300 additions & 0 deletions samples/SkiaSharpDemo.Blazor/Pages/Touch.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
@page "/touch"

<PageTitle>Touch</PageTitle>

<h1>Touch Events</h1>

<p>
Draw on the canvas below with mouse, finger, or stylus.
Each device type draws in a different color. Action counters and an event log update live.
</p>

<div class="toolbar">
<button class="btn btn-sm btn-outline-danger" @onclick="Clear">Clear</button>
<span class="text-muted ms-2">Strokes: @_strokes.Count</span>
</div>

<div class="canvas-container">
<SKTouchCanvasView OnPaintSurface="OnPaintSurface"
Touch="OnTouch"
EnableTouchEvents="true"
style="width: 100%; height: 100%;" />
</div>

<div class="stats-row">
<div class="stats-group">
<div class="stats-header">Device Types</div>
<div class="event-grid">
@foreach (var device in DeviceColors)
{
var count = _deviceCounts.GetValueOrDefault(device.Name);
<div class="event-card">
<span class="color-dot" style="background: @device.Css"></span>
<div class="event-name">@device.Name</div>
<div class="event-count">@count</div>
</div>
}
</div>
</div>
<div class="stats-group">
<div class="stats-header">Touch Actions</div>
<div class="event-grid">
@foreach (var action in ActionNames)
{
var count = _actionCounts.GetValueOrDefault(action);
var active = _lastAction == action;
<div class="event-card @(active ? "active" : "")">
<div class="event-name">@action</div>
<div class="event-count">@count</div>
</div>
}
</div>
</div>
</div>

<div class="log-section">
<div class="stats-header">Event Log <button class="btn btn-sm btn-link" @onclick="ClearLog">clear</button></div>
<div class="event-log">
@foreach (var entry in _log)
{
<div class="log-entry">@entry</div>
}
@if (_log.Count == 0)
{
<div class="log-entry text-muted">Interact with the canvas…</div>
}
</div>
</div>

<style>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot can you move the css into a separate file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved to Touch.razor.css in 026c1f8.

.toolbar {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.canvas-container {
width: 100%;
height: 480px;
border: 1px solid #ccc;
border-radius: 6px;
overflow: hidden;
margin-bottom: 1rem;
}
.stats-row {
display: flex;
gap: 2rem;
flex-wrap: wrap;
margin-bottom: 1rem;
}
.stats-group {
flex: 1;
min-width: 260px;
}
.stats-header {
font-weight: 600;
font-size: 0.85rem;
margin-bottom: 0.4rem;
color: #555;
}
.event-grid {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.event-card {
border: 1px solid #ddd;
border-radius: 4px;
padding: 0.35rem 0.75rem;
min-width: 90px;
text-align: center;
transition: background-color 0.15s, border-color 0.15s;
}
.event-card.active {
background-color: #e0f0ff;
border-color: #3399ff;
}
.color-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-bottom: -1px;
}
.event-name {
font-weight: 600;
font-size: 0.8rem;
}
.event-count {
font-size: 1.1rem;
color: #333;
}
.log-section {
margin-bottom: 2rem;
}
.event-log {
font-family: monospace;
font-size: 0.78rem;
max-height: 160px;
overflow-y: auto;
border: 1px solid #eee;
border-radius: 4px;
padding: 0.4rem 0.6rem;
background: #fafafa;
}
.log-entry {
white-space: nowrap;
}
</style>

@code {
// ── Colors per device type ──

private static readonly (string Name, string Css, SKColor Sk)[] DeviceColors =
{
("Mouse", "#6495ED", new SKColor(0xFF6495ED)), // CornflowerBlue
("Touch", "#FF7F50", new SKColor(0xFFFF7F50)), // Coral
("Stylus", "#3CB371", new SKColor(0xFF3CB371)), // MediumSeaGreen
};

private static readonly string[] ActionNames =
{ "Entered", "Pressed", "Moved", "Released", "Exited", "Cancelled", "WheelChanged" };

// ── Stroke data ──

private record struct StrokeSegment(SKPoint Point);

private class Stroke
{
public SKTouchDeviceType DeviceType { get; init; }
public List<SKPoint> Points { get; } = new();
}

private readonly List<Stroke> _strokes = new();
private readonly Dictionary<long, Stroke> _activeStrokes = new();

// ── Counters ──

private readonly Dictionary<string, int> _actionCounts = new();
private readonly Dictionary<string, int> _deviceCounts = new();
private string _lastAction = "";

// ── Event log ──

private const int MaxLogEntries = 80;
private readonly List<string> _log = new();

// ── Touch handler ──

private void OnTouch(SKTouchEventArgs e)
{
// Count action
var actionName = e.ActionType.ToString();
_actionCounts[actionName] = _actionCounts.GetValueOrDefault(actionName) + 1;
_lastAction = actionName;

// Count device
var deviceName = e.DeviceType.ToString();
_deviceCounts[deviceName] = _deviceCounts.GetValueOrDefault(deviceName) + 1;

// Log (newest first, cap at MaxLogEntries)
var logText = $"{actionName,-14} {deviceName,-7} ({e.Location.X:F0}, {e.Location.Y:F0})";
if (e.ActionType == SKTouchAction.WheelChanged)
logText += $" Δ={e.WheelDelta}";
_log.Insert(0, logText);
if (_log.Count > MaxLogEntries)
_log.RemoveAt(_log.Count - 1);

// Drawing strokes
switch (e.ActionType)
{
case SKTouchAction.Pressed:
var stroke = new Stroke { DeviceType = e.DeviceType };
stroke.Points.Add(e.Location);
_strokes.Add(stroke);
_activeStrokes[e.Id] = stroke;
break;

case SKTouchAction.Moved:
if (_activeStrokes.TryGetValue(e.Id, out var active))
active.Points.Add(e.Location);
break;

case SKTouchAction.Released:
case SKTouchAction.Cancelled:
if (_activeStrokes.TryGetValue(e.Id, out var finished))
{
finished.Points.Add(e.Location);
_activeStrokes.Remove(e.Id);
}
break;
}

e.Handled = true;
}

// ── Paint ──

private void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);

using var paint = new SKPaint
{
IsAntialias = true,
StrokeWidth = 3,
Style = SKPaintStyle.Stroke,
StrokeCap = SKStrokeCap.Round,
StrokeJoin = SKStrokeJoin.Round,
};

foreach (var stroke in _strokes)
{
if (stroke.Points.Count < 2)
continue;

paint.Color = GetDeviceColor(stroke.DeviceType);

using var path = new SKPath();
path.MoveTo(stroke.Points[0]);
for (int i = 1; i < stroke.Points.Count; i++)
path.LineTo(stroke.Points[i]);
canvas.DrawPath(path, paint);
}

// Legend in top-left corner
using var legendFont = new SKFont { Size = 12, Edging = SKFontEdging.SubpixelAntialias };
using var legendPaint = new SKPaint { IsAntialias = true };
var y = 16f;
foreach (var (name, _, color) in DeviceColors)
{
legendPaint.Color = color;
canvas.DrawCircle(12, y - 4, 5, legendPaint);
legendPaint.Color = SKColors.Black;
canvas.DrawText(name, 22, y, SKTextAlign.Left, legendFont, legendPaint);
y += 18;
}
}

private static SKColor GetDeviceColor(SKTouchDeviceType type) => type switch
{
SKTouchDeviceType.Mouse => DeviceColors[0].Sk,
SKTouchDeviceType.Touch => DeviceColors[1].Sk,
SKTouchDeviceType.Stylus => DeviceColors[2].Sk,
_ => SKColors.Gray,
};

// ── Clear ──

private void Clear()
{
_strokes.Clear();
_activeStrokes.Clear();
_actionCounts.Clear();
_deviceCounts.Clear();
_lastAction = "";
_log.Clear();
}

private void ClearLog() => _log.Clear();
}
1 change: 1 addition & 0 deletions samples/SkiaSharpDemo.Blazor/SkiaSharpDemo.Blazor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

<ItemGroup>
<ProjectReference Include="..\..\source\SkiaSharp.Extended\SkiaSharp.Extended.csproj" />
<ProjectReference Include="..\..\source\SkiaSharp.Extended.UI.Blazor\SkiaSharp.Extended.UI.Blazor.csproj" />
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions samples/SkiaSharpDemo.Blazor/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
@using SkiaSharp
@using SkiaSharp.Extended
@using SkiaSharp.Views.Blazor
@using SkiaSharp.Extended.UI.Blazor
@using SkiaSharp.Extended.UI.Blazor.Controls
@using SkiaSharpDemo.Blazor
@using SkiaSharpDemo.Blazor.Layout
1 change: 1 addition & 0 deletions scripts/SkiaSharp.Extended-Pack.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"solution": {
"path": "..\\SkiaSharp.Extended.sln",
"projects": [
"source\\SkiaSharp.Extended.UI.Blazor\\SkiaSharp.Extended.UI.Blazor.csproj",
"source\\SkiaSharp.Extended.UI.Maui\\SkiaSharp.Extended.UI.Maui.csproj",
"source\\SkiaSharp.Extended\\SkiaSharp.Extended.csproj",
]
Expand Down
2 changes: 2 additions & 0 deletions scripts/SkiaSharp.Extended-Test.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
"solution": {
"path": "..\\SkiaSharp.Extended.sln",
"projects": [
"source\\SkiaSharp.Extended.UI.Blazor\\SkiaSharp.Extended.UI.Blazor.csproj",
"source\\SkiaSharp.Extended.UI.Maui\\SkiaSharp.Extended.UI.Maui.csproj",
"source\\SkiaSharp.Extended\\SkiaSharp.Extended.csproj",
"tests\\SkiaSharp.Extended.Tests\\SkiaSharp.Extended.Tests.csproj",
"tests\\SkiaSharp.Extended.UI.Blazor.Tests\\SkiaSharp.Extended.UI.Blazor.Tests.csproj",
"tests\\SkiaSharp.Extended.UI.Maui.Tests\\SkiaSharp.Extended.UI.Maui.Tests.csproj",
]
}
Expand Down
Loading
Loading