Handling Gestures in DrawnUI
DrawnUI gesture docs have two layers:
- the canvas host must be configured to accept interaction
- the control tree decides what to do with taps, pans, long presses, and commands
If you skip the first layer, control handlers can be correct and still never fire.
Start with the canvas host
MAUI Canvas
In MAUI, set the Gestures property on the Canvas that hosts your DrawnUI tree.
Common values:
Enabledfor normal interactive UISoftLockwhen the canvas lives inside a nativeScrollViewand you need DrawnUI panning controls to cooperate with native scrollingLockwhen the DrawnUI surface should fully capture the input stream
Minimal MAUI example:
<draw:Canvas
Gestures="Enabled"
HorizontalOptions="Fill"
VerticalOptions="Fill">
<draw:SkiaLayout Type="Column" Padding="24" Spacing="12">
<draw:SkiaLabel Text="Canvas gestures enabled" FontSize="22" />
<draw:SkiaButton Text="Tap me" Clicked="OnButtonClicked" />
</draw:SkiaLayout>
</draw:Canvas>
Without Gestures="Enabled" or another non-disabled mode, your DrawnUI controls do not receive touch interaction through that canvas.
Blazor WebAssembly Canvas
Browser-side Blazor uses the same idea, but the property is passed as GesturesMode.
Minimal Blazor example:
@using DrawnUi.Draw
@using DrawnUi.Views
<Canvas RootControl="@RootControl"
WidthRequest="400"
HeightRequest="220"
Gestures="@GesturesMode.Enabled" />
@code {
private readonly SkiaControl RootControl = new SkiaLayout()
{
Margin = new Thickness(16),
Type = LayoutType.Column,
Spacing = 12,
Children =
{
new SkiaLabel { Text = "Blazor canvas gestures enabled", FontSize = 22 },
new SkiaButton("Increment").OnTapped(_ => clickCount++)
}
};
private int clickCount;
}
For browser-side Canvas, the default is effectively disabled until you opt in with GesturesMode.Enabled or GesturesMode.Lock.
Blazor Server Canvas
Blazor Server is different.
The server-backed Canvas does not expose the same Gestures parameter as browser-side Canvas. The surface is rendered on the server and interactions are routed back to the server through the server runtime.
That means:
- you do not enable gestures with a
Gesturesproperty on the serverCanvas - you still wire gesture-aware controls and handlers inside the DrawnUI tree
- the same control-level concepts apply, but the host model is the server-backed
Canvas, not browserCanvas
Minimal server-side example:
<Canvas Content="@BuildCanvasContent()"
WidthRequest="400"
HeightRequest="240"
Alt="DrawnUI server-rendered sample" />
@code {
private int clickCount;
private SkiaControl BuildCanvasContent()
{
return new SkiaLayout()
{
Type = LayoutType.Column,
Spacing = 12,
Children =
{
new SkiaLabel { Text = "Server Canvas sample", FontSize = 24 },
new SkiaButton("Increment").OnTapped(_ => clickCount++)
}
};
}
}
So yes, the same control-level gesture patterns still apply to Blazor Server, but the canvas-level enablement step is specific to MAUI Canvas and browser-side Blazor Canvas.
DrawnUi.Net
DrawnUi.Net is different again because it is not a UI framework host by itself.
There is no MAUI Canvas, browser Canvas, or server-backed Blazor Canvas that automatically captures pointer input for you. DrawnUi.Net gives you the DrawnUI rendering and layout model, but the outer host or harness is responsible for delivering input.
That means:
- there is no canvas-level
Gesturesproperty to enable inDrawnUi.Net - control-level gesture logic still works once your host forwards pointer or touch input into the DrawnUI tree
- in a headless rendering harness, there may be no live gesture stream at all unless you explicitly simulate or inject it
Use DrawnUi.Net for gesture-related work when you want to:
- validate shared gesture state transitions in a controlled harness
- reproduce a gesture bug outside MAUI or Blazor noise
- test the DrawnUI control logic after input has already been normalized by your own host
So the rule is:
- MAUI and browser Blazor need host-level gesture enablement on the canvas surface
- Blazor Server uses its server-backed
Canvashost behavior instead of aGesturesproperty DrawnUi.Netdepends on whatever outer host or test harness you build around it to feed interaction into DrawnUI
Then wire control-level gestures
Once the host surface is configured correctly, choose the control-level pattern that matches the job.
ConsumeGestures for raw gesture handling
Use ConsumeGestures when you want to inspect tap, pan, long press, or release events directly.
<draw:SkiaShape
x:Name="MyCard"
Type="Rectangle"
CornerRadius="20"
ConsumeGestures="OnCardGestures">
<!-- Your content here -->
</draw:SkiaShape>
private void OnCardGestures(object sender, SkiaGesturesInfo e)
{
if (sender is not SkiaControl control)
{
return;
}
switch (e.Args.Type)
{
case TouchActionResult.Tapped:
e.Consumed = true;
Task.Run(async () =>
{
await control.ScaleTo(1.05, 80);
await control.ScaleTo(1.0, 80);
});
break;
case TouchActionResult.Panning:
e.Consumed = true;
control.TranslationX += e.Args.Event.Distance.Delta.X / control.RenderingScale;
control.TranslationY += e.Args.Event.Distance.Delta.Y / control.RenderingScale;
break;
case TouchActionResult.Up:
e.Consumed = true;
Task.Run(async () => await control.TranslateToAsync(0, 0, 180));
break;
}
}
Key points:
ConsumeGesturesgives you raw gesture events on the control- check
e.Args.TypeforTapped,Panning,Up,LongPressing, and related states - set
e.Consumed = truewhen you want to stop propagation - keep the handler synchronous; if you animate, start async work from inside it
SkiaButton for button-like taps
For button-style interaction, prefer the higher-level button APIs.
XAML event handler
<draw:SkiaButton
Text="Click Me"
Tapped="OnButtonTapped" />
private void OnButtonTapped(object sender, ControlTappedEventArgs e)
{
// Handle button click
}
Commands with AddGestures
<draw:SkiaButton
Text="Click Me"
draw:AddGestures.CommandTapped="{Binding MyCommand}" />
MVVM shape or layout gestures
For non-button surfaces, attach commands to shapes and layouts:
<draw:SkiaShape
Type="Rectangle"
draw:AddGestures.CommandTapped="{Binding SelectItemCommand}"
draw:AddGestures.CommandTappedParameter="{Binding .}"
draw:AddGestures.AnimationTapped="Scale">
<draw:SkiaLabel Text="{Binding Name}" />
</draw:SkiaShape>
Built-in AnimationTapped values include:
ScaleRippleFade
Gesture locking and propagation
Use LockChildrenGestures when a parent layout should decide which gestures reach nested controls.
<draw:SkiaLayout Type="Column" LockChildrenGestures="PassTap">
<draw:SkiaShape Type="Rectangle" ConsumeGestures="OnTap" />
</draw:SkiaLayout>
Practical routing
Use this rule of thumb:
- configure the host first:
Canvas.Gesturesin MAUI orGesturesModein browser Blazor - use
SkiaButtonevents or commands for button-like actions - use
ConsumeGestureswhen you need low-level gesture state such as pan, long press, or release - on Blazor Server, use
Canvasplus the same control-level handlers inside the DrawnUI tree
Options:
Enabled: Children can't receive gesturesDisabled: All gestures pass through (default)PassTap: Only tap/click events reach childrenPassTapAndLongPress: Tap and long-press pass through
Common Patterns
Tap Feedback (Scale Animation)
private void OnTap(object sender, SkiaGesturesInfo e)
{
if (e.Args.Type == TouchActionResult.Tapped)
{
e.Consumed = true;
Task.Run(async () =>
{
var control = (SkiaControl)sender;
await control.ScaleTo(0.95, 100);
await control.ScaleTo(1.0, 100);
});
}
}
Swipe Detection
private void OnSwipe(object sender, SkiaGesturesInfo e)
{
if (e.Args.Type == TouchActionResult.Up)
{
e.Consumed = true;
// Check swipe distance and direction
var totalDistance = e.Args.Event.Distance.Total;
if (Math.Abs(totalDistance.X) > 100 && Math.Abs(totalDistance.X) > Math.Abs(totalDistance.Y))
{
// Horizontal swipe
if (totalDistance.X > 0)
{
// Swiped right
}
else
{
// Swiped left
}
}
}
}
Long Press Menu
private void OnLongPress(object sender, SkiaGesturesInfo e)
{
if (e.Args.Type == TouchActionResult.LongPressing)
{
e.Consumed = true;
// Show context menu at gesture location
var position = new Point(e.Args.Event.Location.X, e.Args.Event.Location.Y);
ShowContextMenu(position);
}
}
Key Takeaways
- Use
ConsumeGesturesfor most UI interactions - It's simple, clean, and no subclassing required - Keep handlers synchronous - Always use
Task.Run()for async work like animations - Check gesture type with
e.Args.Type- This tells you what action occurred (Tapped, Panning, Up, etc.) - Set
e.Consumed = true- This prevents the gesture from bubbling to parent controls - Access gesture data from
e.Args.Event- Location, distance, pinch scale all available here - Use
AddGesturesfor MVVM - When you need command binding instead of code-behind - Use
LockChildrenGesturesto manage propagation - Control which gestures reach nested controls
For additional gesture utilities, see the helper methods in Canvas.cs and SkiaControl.Shared.cs for GetGesturePositionInsideControl(), GetGesturePositionInsideChild(), and CheckChildGestureHit().