| uti | id | title | platforms | packages | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
com.xamarin.workbook  | 
  3f9c2e8c-577a-47e1-bd7f-8ec872ff5c29  | 
  SkiaSharp on iOS  | 
  
  | 
  
  | 
  
#r "SkiaSharp"#r "SkiaSharp.Views.iOS"// SkiaSharp usings
using SkiaSharp;
using SkiaSharp.Views;
using SkiaSharp.Views.iOS;
//
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
// iOS usings
using UIKit;var controller = RootViewController;public abstract class Sprite 
{
    public Action SetNeedsDisplay { get; set; }
    public Sprite()
    {
        SetNeedsDisplay = () => { Debug.WriteLine(this.ToString() + " Needs Display"); };
    }
    Lazy<uint> randColor => new Lazy<uint>(() => (uint)(new Random(this.GetType().Name.GetHashCode()).NextDouble() * 0xFFFFFFFF));
    protected virtual SKPaint BackgroundPaint { get; } = new SKPaint {
        Color = 0x00000000 //SkColors.Transparent
    };
    public void Draw(SKCanvas canvas, SKRect size, bool isDebug=false)
    {
        var rect = GetRect(size);
        DrawRect(canvas, rect);
        if (isDebug)
        {
            canvas.DrawRect(rect, new SKPaint 
            {
                Color = new SKColor(randColor.Value),
                Style = SKPaintStyle.Stroke,
            });
        }
    }
    protected virtual void DrawRect(SKCanvas canvas, SKRect size)
    {
        canvas.DrawRect(size, BackgroundPaint);
    }
    public abstract SKRect GetRect(SKRect canvasRect);
}
public abstract class Button : Sprite
{
    protected Action<Button> OnPressAction { get; set; }
    public void OnPress()
    {
        OnPressAction?.Invoke(this);
        Console.WriteLine(this);
        SetNeedsDisplay?.Invoke();
    }
}
public abstract class Label : Button
{
    protected abstract string Text { get; }
    protected abstract SKPaint Paint { get; }
    protected override void DrawRect(SKCanvas canvas, SKRect size)
    {
        base.DrawRect(canvas, size);
        canvas.DrawText(
            Text, 
            x: size.Left + (size.Width/2),
            y: size.Top + (size.Height/2) + (Paint.TextSize/2),
            paint:Paint
        );
    }
}
public class LabelImpl : Label
{
    public LabelImpl(Func<string> text, SKPaint paint)
    {
        _text = text;
        _paint = paint;
    }
    private Func<string> _text;
    protected override string Text => _text?.Invoke();
    private SKPaint _paint;
    protected override SKPaint Paint => _paint;
    public override SKRect GetRect(SKRect canvasRect)
    {
        return new SKRect(canvasRect.Left, canvasRect.Top, canvasRect.Width, Paint.TextSize *2);
    }
}// create the view
var skiaCanvasView = new SKCanvasView(controller.View.Bounds);
// add it to the window
controller.View.AddSubview(skiaCanvasView);void SetAndNotify<T>(/*Func<T> prop,*/ T value, SKCanvasView view)
{
    view.SetNeedsDisplay();
}
void SetNeedsDisplay(Sprite sprite, SKCanvasView view)
{
    var rect = CGRect.Empty;//TODO : from view.GetRect()
    view.SetNeedsDisplayInRect(rect);
}//static class DateTimeExtensions {
    static GregorianCalendar _gc = new GregorianCalendar();
    public static int GetDayPositionInWeek(this DateTime time)
        => (((int)time.DayOfWeek)+6)%7; //TODO : depends on the culture ?
    
    public static int GetWeekOfMonth(this DateTime time, DateTime first) 
    {
        return time.GetWeekOfYear() - first.GetWeekOfYear();
    }
    static int GetWeekOfYear(this DateTime time)
        => _gc.GetWeekOfYear(time, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
//}public class Paints
{
    private static SKTypeface font = SKTypeface.FromFamilyName("Trebuchet");
    public SKPaint paint = new SKPaint {
        IsAntialias = true,
        TextSize = 42,
        TextAlign = SKTextAlign.Center,
        Color = 0xFF000000,
        Style = SKPaintStyle.Fill,
        Typeface = font,
    };
    public SKPaint paintToday = new SKPaint {
        IsAntialias = true,
        TextSize = 42,
        TextAlign = SKTextAlign.Center,
        Color = 0xFF0000FF,
        Style = SKPaintStyle.Fill,
        Typeface = font,
    };
    public SKPaint paintWE = new SKPaint {
        IsAntialias = true,
        TextSize = 42,
        TextAlign = SKTextAlign.Center,
        Color = 0xFFB0B0B0,
        Style = SKPaintStyle.Fill,
        Typeface = font,
    };
    public SKPaint paintSelected = new SKPaint {
        IsAntialias = true,
        Color = 0xFFB0B0B0,
        Style = SKPaintStyle.Fill,
    };
}
var paints = new Paints();class Day : Label
{
    Paints _paints;
    int _selectedMonth;
    public DateTime Date { get; private set; }
    public Day(DateTime d, int selectedMonth, Paints paints, Action<DateTime> onPress, bool isSelected = false)
    {
        Date = d;
        _paints = paints;
        _selectedMonth = selectedMonth;
        OnPressAction = bt => onPress?.Invoke(Date);
        IsSelected = isSelected;
    }
    public int DayPos => Date.GetDayPositionInWeek();
    public int WeekPos => Date.GetWeekOfMonth(new DateTime(Date.Year, _selectedMonth, 1));
    public bool IsInWeekEnd => Date.DayOfWeek == DayOfWeek.Saturday || Date.DayOfWeek == DayOfWeek.Sunday;
    public bool IsOutsideOfSelectedMonth => Date.Month != _selectedMonth;
    public bool IsToday => Date.Date == DateTime.Now.Date;
    public bool IsPast => Date.Date < DateTime.Now.Date;
    public bool IsSelected { get; set; } = false;
    protected override string Text => Date.Day.ToString();
    protected override SKPaint Paint => 
        IsToday 
            ? _paints.paintToday 
            : IsInWeekEnd || IsOutsideOfSelectedMonth || IsPast
                ? _paints.paintWE
                : _paints.paint;
    protected override SKPaint BackgroundPaint => 
        IsSelected
            ? _paints.paintSelected
            : base.BackgroundPaint;
    public override SKRect GetRect (SKRect size)
    {
        size.Top = 80;
        var w = size.Width / 7;
        var h = size.Height / 5; //TODO get totalWeeks instead of 5
        return SKRect.Create(
            x: size.Left + (DayPos * w),
            y: size.Top + (WeekPos * h),
            width: w,
            height: h
        );
    }
}class PrevNext : Label
{
    int _direction;
    private string _label;
    protected override string Text => _label;
    private SKPaint _paint;
    protected override SKPaint Paint => _paint; 
    public PrevNext(string label, int direction, SKPaint paint, Action<int> action)
    {
        _label = label;
        _direction = direction;
        _paint = paint;
        this.OnPressAction = bt => action?.Invoke(direction);
    }
    public override SKRect GetRect (SKRect size)
    {
        var w = 64;
        var h = 64;
        return SKRect.Create(
            x: _direction < 0 ? 0 : size.Width - w,
            y: size.Top,
            width: w,
            height: h
        );
    }
}var displayList = new List<Sprite>();DateTime selectedDate, selectedMonth;
selectedDate = selectedMonth = DateTime.Now.Date;
//DateTime SelectedDate { get => selectedDate; set => SetAndNotify(/*v=>selectedDate=v,*/ value, skiaCanvasView); }
void ChangeMonth(int direction)
{
    selectedMonth = new DateTime(selectedMonth.Year, selectedMonth.Month, 1).AddMonths(direction); // TODO: selectedmonth + redraw
    var firstDay = new DateTime(selectedMonth.Year, selectedMonth.Month, 1);
    var firstDayOfFirstWeek = firstDay.AddDays(-firstDay.GetDayPositionInWeek());
    var lastDay = new DateTime(selectedMonth.Year, selectedMonth.Month, DateTime.DaysInMonth(selectedMonth.Year, selectedMonth.Month));
    var lastDayOfLastWeek = lastDay.AddDays(7 - 1 - lastDay.GetDayPositionInWeek());
    var totalDays = (lastDayOfLastWeek - firstDayOfFirstWeek).Days + 1;
    var totalWeeks = (totalDays) / 7;
    displayList.RemoveAll(s => s is Day);
    for (int d=0; d < totalDays; d++){
        var day = firstDayOfFirstWeek.AddDays(d);
        displayList.Add(new Day(day, selectedMonth.Month, paints, SelectDate, selectedDate.Date == day.Date));
    }
    skiaCanvasView.SetNeedsDisplay();
}
void SelectDate(DateTime time)
{
    selectedDate = time;
    var days = displayList.Where(s => s is Day);
    foreach (Sprite s in days)
        (s as Day).IsSelected = (s as Day).Date.Date == time.Date;
    //TODO shouldn't be needed, SetNeedsDisplay from Sprite should call it
    skiaCanvasView.SetNeedsDisplay();
}ChangeMonth(0);displayList.Add(new LabelImpl(() => selectedMonth.ToString("MMMM yyyy"), paints.paint));
displayList.Add(new PrevNext("<", -1, paints.paint, ChangeMonth));
displayList.Add(new PrevNext(">", +1, paints.paint, ChangeMonth));displayListSKRect rect;T GetSpriteAt<T>(SKPoint point, SKRect rect)
    where T:Sprite
    => displayList
        .Where(s => s is T)
        .LastOrDefault(s => s.GetRect(rect).Contains(point))
        as T
        ;void Draw(SKCanvas canvas, SKSize size)
{
    canvas.Clear(SKColors.White);
    rect = new SKRect(0, 0, size.Width, size.Height/2);
    // display list
    foreach(Sprite sprite in displayList)
    {
        sprite.SetNeedsDisplay = () => SetNeedsDisplay(sprite, skiaCanvasView);
        sprite.Draw(canvas, rect, isDebug:false);
    }
}//var b = GetSpriteAt<Button>(new SKPoint(200, 200), rect);skiaCanvasView.PaintSurface += OnPaintSurface;
void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
    var canvas = e.Surface.Canvas;
    var size = e.Info.Size;
    Draw(canvas, size);
}
// trigger a refresh
skiaCanvasView.SetNeedsDisplay();public class SKTouchEventArgs : EventArgs
{
    public SKTouchEventArgs(long id, SKTouchAction type, SKPoint location, bool inContact)
        : this(id, type, SKMouseButton.Left, SKTouchDeviceType.Touch, location, inContact)
    {
    }
    public SKTouchEventArgs(long id, SKTouchAction type, SKMouseButton mouseButton, SKTouchDeviceType deviceType, SKPoint location, bool inContact)
    {
        Id = id;
        ActionType = type;
        DeviceType = deviceType;
        MouseButton = mouseButton;
        Location = location;
        InContact = inContact;
    }
    public bool Handled { get; set; }
    public long Id { get; private set; }
    public SKTouchAction ActionType { get; private set; }
    public SKTouchDeviceType DeviceType { get; private set; }
    public SKMouseButton MouseButton { get; private set; }
    public SKPoint Location { get; private set; }
    public bool InContact { get; private set; }
    public override string ToString()
    {
        return $"{{ActionType={ActionType}, DeviceType={DeviceType}, Handled={Handled}, Id={Id}, InContact={InContact}, Location={Location}, MouseButton={MouseButton}}}";
    }
}
public enum SKTouchAction
{
    Entered,
    Pressed,
    Moved,
    Released,
    Cancelled,
    Exited,
}
public enum SKTouchDeviceType
{
    Touch,
    Mouse,
    Pen
}
public enum SKMouseButton
{
    Unknown,
    Left,
    Middle,
    Right
}skiaCanvasView.AddGestureRecognizer(new PressGestureReco(skiaCanvasView, OnSliderTouchOrMove));
float x,y; 
private void OnSliderTouchOrMove(SKTouchEventArgs sk)
{
    x = sk.Location.X * (float)skiaCanvasView.ContentScaleFactor;
    y = sk.Location.Y * (float)skiaCanvasView.ContentScaleFactor;
    var b = GetSpriteAt<Button>(new SKPoint(x, y), rect);
    b?.OnPress();
}
private class PressGestureReco : UITapGestureRecognizer
{
    private readonly SKCanvasView _view;
    private readonly Action<SKTouchEventArgs> _onTouchAction;
    public PressGestureReco(SKCanvasView view, Action<SKTouchEventArgs> onTouchAction)
    {
        _view = view;
        _onTouchAction = onTouchAction;
    }
    public override void TouchesBegan(NSSet touches, UIEvent evt)
    {
        base.TouchesBegan(touches, evt);
        UITouch t = touches.AnyObject as UITouch;
        var point = t.LocationInView(_view);
        _onTouchAction(new SKTouchEventArgs(0, SKTouchAction.Moved,
            new SKPoint((float)point.X, (float)point.Y), true));
    }
}GetVars()displayList[19].GetRect(rect);GetSpriteAt<Button>(new SKPoint(x, y), rect)