Skip to content

Instantly share code, notes, and snippets.

@benoitjadinon
Last active August 9, 2018 07:28
Show Gist options
  • Save benoitjadinon/6015a1e2802c87796ace7e55e81780ab to your computer and use it in GitHub Desktop.
Save benoitjadinon/6015a1e2802c87796ace7e55e81780ab to your computer and use it in GitHub Desktop.
skiasharp ios calendar wip
uti id title platforms packages
com.xamarin.workbook
3f9c2e8c-577a-47e1-bd7f-8ec872ff5c29
SkiaSharp on iOS
iOS
id version
SkiaSharp
1.60.2
id version
SkiaSharp.Views
1.60.2

SkiaSharp Calendar

#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));
displayList
SKRect 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)
@benoitjadinon
Copy link
Author

iphone_5s_-_11_4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment