// TV prize wheel — SkiaSpinner sectors, gift icons riding on top. Drag the wheel or tap SPIN.
// Note: a custom ItemTemplate currently blanks SkiaSpinner (it re-declares the base ItemTemplateProperty),
// so the sectors use the built-in template and the gifts are drawn over the wheel, following WheelRotation.
var prizes = new System.Collections.ObjectModel.ObservableCollection<string>
{
"CAR", "$1000", "PHONE", "HOLIDAY", "SMART TV",
"BIKE", "COFFEE", "TRY AGAIN", "GIFT CARD", "MYSTERY"
};
// a wrapped present, rasterised once and stamped into every sector
const string giftSvg =
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>" +
"<rect x='14' y='42' width='72' height='46' rx='6' fill='#FFFFFF'/>" +
"<rect x='14' y='42' width='72' height='13' rx='5' fill='#FFE08A'/>" +
"<rect x='44' y='42' width='13' height='46' fill='#E5484D'/>" +
"<path d='M50 42 C40 42 29 36 29 28 C29 21 35 17 41 19 C47 21 50 32 50 42 Z' fill='#E5484D'/>" +
"<path d='M50 42 C60 42 71 36 71 28 C71 21 65 17 59 19 C53 21 50 32 50 42 Z' fill='#E5484D'/>" +
"<circle cx='50' cy='38' r='6' fill='#FFE08A'/></svg>";
var giftDoc = new Svg.Skia.SKSvg();
giftDoc.FromSvg(giftSvg);
var gift = giftDoc.Picture;
SkiaSpinner wheel = null;
SkiaLabel result = null;
const double wheelSize = 300;
return new SkiaShape
{
CornerRadius = 16,
BackgroundColor = Color.Parse("#0E1424"),
WidthRequest = 380,
HeightRequest = 470,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Children = new List<SkiaControl>
{
new SkiaLabel("PRIZE WHEEL")
{
FontSize = 20,
FontWeight = 800,
TextColor = Color.Parse("#FFD166"),
Margin = new Thickness(0, 18, 0, 0),
}.CenterX(),
// stage: bulbs, wheel, gifts, pointer, hub
new SkiaLayer
{
WidthRequest = 340,
HeightRequest = 340,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Start,
Margin = new Thickness(0, 54, 0, 0),
Children = new List<SkiaControl>
{
// bulbs around the rim, like a game show
new SkiaLayer()
.Fill()
.WhenPaint((me, ctx) =>
{
var s = (float)ctx.Scale;
var d = ctx.Destination;
var r = Math.Min(d.Width, d.Height) / 2 - 5 * s;
using var bulb = new SKPaint { IsAntialias = true };
for (int i = 0; i < 24; i++)
{
var a = i * Math.PI * 2 / 24;
bulb.Color = i % 2 == 0 ? SKColor.Parse("#FFD166") : SKColor.Parse("#8A6D1F");
ctx.Context.Canvas.DrawCircle(
(float)(d.MidX + Math.Cos(a) * r),
(float)(d.MidY + Math.Sin(a) * r),
3.5f * s, bulb);
}
}),
new SkiaSpinner
{
ItemsSource = prizes,
Velocity = 2.0,
InverseVisualRotation = true,
SidePosition = SidePosition.Left,
WidthRequest = wheelSize,
HeightRequest = wheelSize,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
}.Assign(out wheel),
// one gift per sector, kept in step with the wheel's own rotation
new SkiaLayer()
.Fill()
.WhenPaint((me, ctx) =>
{
if (gift == null || wheel == null) return;
var canvas = ctx.Context.Canvas;
var s = (float)ctx.Scale;
var d = ctx.Destination;
var radius = wheelSize / 2 * 0.84 * s; // out near the rim, clear of the sector text
var step = 360.0 / prizes.Count;
var icon = 22 * s;
for (int i = 0; i < prizes.Count; i++)
{
var deg = wheel.WheelRotation - (i * step - 90);
var rad = deg * Math.PI / 180;
canvas.Save();
canvas.Translate(
(float)(d.MidX + Math.Cos(rad) * radius),
(float)(d.MidY + Math.Sin(rad) * radius));
canvas.RotateDegrees((float)deg);
canvas.Scale(icon / 100f);
canvas.Translate(-50, -50);
canvas.DrawPicture(gift);
canvas.Restore();
}
})
.WithGestures((me, args, apply) =>
{
// claim the gesture on Down, otherwise the wheel owns the whole sequence
// and the Tapped that follows never reaches this overlay
if (args.Type != TouchActionResult.Down && args.Type != TouchActionResult.Up) return null;
var scale = me.RenderingScale;
var dx = (args.Event.Location.X - me.DrawingRect.MidX) / scale;
var dy = (args.Event.Location.Y - me.DrawingRect.MidY) / scale;
if (dx * dx + dy * dy > 37 * 37) return null; // outside the hub: wheel's business
// Down only claims the sequence, the spin fires when the finger lifts
if (args.Type == TouchActionResult.Up) wheel.SpinToRandom();
return me;
})
.Animate(1, (me, animator, value, dt) => me.Update(), repeat: -1),
// the pointer the winning sector stops under
new SkiaShape
{
Type = ShapeType.Polygon,
BackgroundColor = Color.Parse("#FFD166"),
StrokeColor = Colors.White,
StrokeWidth = 1.5,
WidthRequest = 26,
HeightRequest = 18,
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Center,
Margin = new Thickness(8, 0, 0, 0),
ZIndex = 10,
Points = new List<SkiaPoint>
{
new SkiaPoint(1, 0.5),
new SkiaPoint(0, 0),
new SkiaPoint(0, 1),
},
},
// hub doubles as the spin button
new SkiaShape
{
Type = ShapeType.Circle,
BackgroundColor = Color.Parse("#151B2E"),
StrokeColor = Color.Parse("#FFD166"),
StrokeWidth = 3,
WidthRequest = 74,
HeightRequest = 74,
ZIndex = 11,
Children = new List<SkiaControl>
{
new SkiaLabel("SPIN")
{
FontSize = 15,
FontWeight = 800,
TextColor = Color.Parse("#FFD166"),
}.Center(),
}
}.Center(),
}
},
new SkiaLabel("tap SPIN or drag the wheel")
{
FontSize = 13,
TextColor = Color.Parse("#8FA3C8"),
Margin = new Thickness(0, 404, 0, 0),
}.CenterX(),
new SkiaLabel("")
{
FontSize = 19,
FontWeight = 800,
TextColor = Colors.White,
Margin = new Thickness(0, 424, 0, 0),
}
.CenterX()
.Assign(out result)
.ObserveProperty(wheel, nameof(SkiaSpinner.SelectedIndex), me =>
{
me.Text = wheel.SelectedIndex >= 0 && wheel.SelectedIndex < prizes.Count
? $"YOU WIN: {prizes[wheel.SelectedIndex]}"
: "";
}),
}
};