2025-12-19 09:30:16 +00:00
|
|
|
// Licensed to the .NET Foundation under one or more agreements.
|
|
|
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
using System;
|
|
|
|
|
using Microsoft.Maui.Controls;
|
2026-01-16 05:14:04 +00:00
|
|
|
using Microsoft.Maui.Graphics;
|
2025-12-21 13:26:56 -05:00
|
|
|
using Microsoft.Maui.Platform.Linux;
|
2026-01-01 13:51:12 -05:00
|
|
|
using SkiaSharp;
|
2025-12-19 09:30:16 +00:00
|
|
|
|
|
|
|
|
namespace Microsoft.Maui.Platform;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Skia-rendered date picker control with calendar popup.
|
2026-01-16 05:14:04 +00:00
|
|
|
/// Implements MAUI IDatePicker interface patterns.
|
2025-12-19 09:30:16 +00:00
|
|
|
/// </summary>
|
|
|
|
|
public class SkiaDatePicker : SkiaView
|
|
|
|
|
{
|
2025-12-21 13:26:56 -05:00
|
|
|
#region BindableProperties
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
public static readonly BindableProperty DateProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(Date), typeof(DateTime), typeof(SkiaDatePicker), DateTime.Today, BindingMode.TwoWay,
|
|
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).OnDatePropertyChanged((DateTime)o, (DateTime)n));
|
2025-12-21 13:26:56 -05:00
|
|
|
|
|
|
|
|
public static readonly BindableProperty MinimumDateProperty =
|
2026-01-01 13:51:12 -05:00
|
|
|
BindableProperty.Create(nameof(MinimumDate), typeof(DateTime), typeof(SkiaDatePicker), new DateTime(1900, 1, 1), BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty MaximumDateProperty =
|
2026-01-01 13:51:12 -05:00
|
|
|
BindableProperty.Create(nameof(MaximumDate), typeof(DateTime), typeof(SkiaDatePicker), new DateTime(2100, 12, 31), BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty FormatProperty =
|
2026-01-01 13:51:12 -05:00
|
|
|
BindableProperty.Create(nameof(Format), typeof(string), typeof(SkiaDatePicker), "d", BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty TextColorProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(SkiaDatePicker), Colors.Black, BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty BorderColorProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgb(189, 189, 189), BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty CalendarBackgroundColorProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(CalendarBackgroundColor), typeof(Color), typeof(SkiaDatePicker), Colors.White, BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty SelectedDayColorProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(SelectedDayColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgb(33, 150, 243), BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty TodayColorProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(TodayColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgba(33, 150, 243, 64), BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty HeaderColorProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(HeaderColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgb(33, 150, 243), BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty DisabledDayColorProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(DisabledDayColor), typeof(Color), typeof(SkiaDatePicker), Color.FromRgb(189, 189, 189), BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
2026-01-16 05:36:22 +00:00
|
|
|
public static readonly BindableProperty FontFamilyProperty =
|
|
|
|
|
BindableProperty.Create(nameof(FontFamily), typeof(string), typeof(SkiaDatePicker), string.Empty, BindingMode.TwoWay,
|
|
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
public static readonly BindableProperty FontSizeProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(FontSize), typeof(double), typeof(SkiaDatePicker), 14.0, BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).InvalidateMeasure());
|
|
|
|
|
|
2026-01-16 05:36:22 +00:00
|
|
|
public static readonly BindableProperty FontAttributesProperty =
|
|
|
|
|
BindableProperty.Create(nameof(FontAttributes), typeof(FontAttributes), typeof(SkiaDatePicker), FontAttributes.None, BindingMode.TwoWay,
|
|
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
public static readonly BindableProperty CharacterSpacingProperty =
|
|
|
|
|
BindableProperty.Create(nameof(CharacterSpacing), typeof(double), typeof(SkiaDatePicker), 0.0, BindingMode.TwoWay,
|
|
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
public static readonly BindableProperty CornerRadiusProperty =
|
2026-01-16 05:14:04 +00:00
|
|
|
BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(SkiaDatePicker), 4.0, BindingMode.TwoWay,
|
2025-12-21 13:26:56 -05:00
|
|
|
propertyChanged: (b, o, n) => ((SkiaDatePicker)b).Invalidate());
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
#region Fields
|
|
|
|
|
|
|
|
|
|
private DateTime _displayMonth;
|
|
|
|
|
private bool _isOpen;
|
|
|
|
|
|
|
|
|
|
private const float CalendarWidth = 280f;
|
|
|
|
|
private const float CalendarHeight = 320f;
|
|
|
|
|
private const float HeaderHeight = 48f;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
#region Properties
|
2025-12-19 09:30:16 +00:00
|
|
|
|
|
|
|
|
public DateTime Date
|
|
|
|
|
{
|
2025-12-21 13:26:56 -05:00
|
|
|
get => (DateTime)GetValue(DateProperty);
|
|
|
|
|
set => SetValue(DateProperty, ClampDate(value));
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public DateTime MinimumDate
|
|
|
|
|
{
|
2025-12-21 13:26:56 -05:00
|
|
|
get => (DateTime)GetValue(MinimumDateProperty);
|
|
|
|
|
set => SetValue(MinimumDateProperty, value);
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public DateTime MaximumDate
|
|
|
|
|
{
|
2025-12-21 13:26:56 -05:00
|
|
|
get => (DateTime)GetValue(MaximumDateProperty);
|
|
|
|
|
set => SetValue(MaximumDateProperty, value);
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string Format
|
|
|
|
|
{
|
2025-12-21 13:26:56 -05:00
|
|
|
get => (string)GetValue(FormatProperty);
|
|
|
|
|
set => SetValue(FormatProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public Color TextColor
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
get => (Color)GetValue(TextColorProperty);
|
2025-12-21 13:26:56 -05:00
|
|
|
set => SetValue(TextColorProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public Color BorderColor
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
get => (Color)GetValue(BorderColorProperty);
|
2025-12-21 13:26:56 -05:00
|
|
|
set => SetValue(BorderColorProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public Color CalendarBackgroundColor
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
get => (Color)GetValue(CalendarBackgroundColorProperty);
|
2025-12-21 13:26:56 -05:00
|
|
|
set => SetValue(CalendarBackgroundColorProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public Color SelectedDayColor
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
get => (Color)GetValue(SelectedDayColorProperty);
|
2025-12-21 13:26:56 -05:00
|
|
|
set => SetValue(SelectedDayColorProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public Color TodayColor
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
get => (Color)GetValue(TodayColorProperty);
|
2025-12-21 13:26:56 -05:00
|
|
|
set => SetValue(TodayColorProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public Color HeaderColor
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
get => (Color)GetValue(HeaderColorProperty);
|
2025-12-21 13:26:56 -05:00
|
|
|
set => SetValue(HeaderColorProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public Color DisabledDayColor
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
get => (Color)GetValue(DisabledDayColorProperty);
|
2025-12-21 13:26:56 -05:00
|
|
|
set => SetValue(DisabledDayColorProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:36:22 +00:00
|
|
|
public string FontFamily
|
|
|
|
|
{
|
|
|
|
|
get => (string)GetValue(FontFamilyProperty);
|
|
|
|
|
set => SetValue(FontFamilyProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public double FontSize
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
get => (double)GetValue(FontSizeProperty);
|
2025-12-21 13:26:56 -05:00
|
|
|
set => SetValue(FontSizeProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:36:22 +00:00
|
|
|
public FontAttributes FontAttributes
|
|
|
|
|
{
|
|
|
|
|
get => (FontAttributes)GetValue(FontAttributesProperty);
|
|
|
|
|
set => SetValue(FontAttributesProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public double CharacterSpacing
|
|
|
|
|
{
|
|
|
|
|
get => (double)GetValue(CharacterSpacingProperty);
|
|
|
|
|
set => SetValue(CharacterSpacingProperty, value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public double CornerRadius
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
get => (double)GetValue(CornerRadiusProperty);
|
2025-12-21 13:26:56 -05:00
|
|
|
set => SetValue(CornerRadiusProperty, value);
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsOpen
|
|
|
|
|
{
|
|
|
|
|
get => _isOpen;
|
2025-12-21 13:26:56 -05:00
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (_isOpen != value)
|
|
|
|
|
{
|
|
|
|
|
_isOpen = value;
|
|
|
|
|
if (_isOpen)
|
2026-01-01 13:51:12 -05:00
|
|
|
SkiaView.RegisterPopupOverlay(this, DrawCalendarOverlay);
|
2025-12-21 13:26:56 -05:00
|
|
|
else
|
2026-01-01 13:51:12 -05:00
|
|
|
SkiaView.UnregisterPopupOverlay(this);
|
2025-12-21 13:26:56 -05:00
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
#endregion
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
#region Events
|
2025-12-21 13:26:56 -05:00
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
public event EventHandler<DateChangedEventArgs>? DateSelected;
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Constructor
|
|
|
|
|
|
|
|
|
|
public SkiaDatePicker()
|
|
|
|
|
{
|
|
|
|
|
IsFocusable = true;
|
|
|
|
|
_displayMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
#region Helper Methods
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Converts a MAUI Color to SkiaSharp SKColor.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static SKColor ToSKColor(Color? color)
|
|
|
|
|
{
|
|
|
|
|
if (color == null) return SKColors.Transparent;
|
2026-01-17 03:36:37 +00:00
|
|
|
return color.ToSKColor();
|
2026-01-16 05:14:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Converts a MAUI Color to SKColor with modified alpha.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static SKColor ToSKColorWithAlpha(Color? color, byte alpha)
|
|
|
|
|
{
|
|
|
|
|
if (color == null) return SKColors.Transparent;
|
2026-01-17 03:36:37 +00:00
|
|
|
return color.ToSKColor().WithAlpha(alpha);
|
2026-01-16 05:14:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
#region Private Methods
|
|
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
private SKRect GetCalendarRect(SKRect pickerBounds)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
int windowWidth = LinuxApplication.Current?.MainWindow?.Width ?? 800;
|
|
|
|
|
int windowHeight = LinuxApplication.Current?.MainWindow?.Height ?? 600;
|
2025-12-21 13:26:56 -05:00
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
float calendarLeft = pickerBounds.Left;
|
|
|
|
|
float calendarTop = pickerBounds.Bottom + 4f;
|
2025-12-21 13:26:56 -05:00
|
|
|
|
|
|
|
|
if (calendarLeft + CalendarWidth > windowWidth)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
calendarLeft = windowWidth - CalendarWidth - 4f;
|
|
|
|
|
}
|
|
|
|
|
if (calendarLeft < 0f)
|
|
|
|
|
{
|
|
|
|
|
calendarLeft = 4f;
|
2025-12-21 13:26:56 -05:00
|
|
|
}
|
|
|
|
|
if (calendarTop + CalendarHeight > windowHeight)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
calendarTop = pickerBounds.Top - CalendarHeight - 4f;
|
|
|
|
|
}
|
|
|
|
|
if (calendarTop < 0f)
|
|
|
|
|
{
|
|
|
|
|
calendarTop = 4f;
|
2025-12-21 13:26:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new SKRect(calendarLeft, calendarTop, calendarLeft + CalendarWidth, calendarTop + CalendarHeight);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
private void OnDatePropertyChanged(DateTime oldValue, DateTime newValue)
|
2025-12-21 13:26:56 -05:00
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
_displayMonth = new DateTime(newValue.Year, newValue.Month, 1);
|
|
|
|
|
DateSelected?.Invoke(this, new DateChangedEventArgs(oldValue, newValue));
|
2025-12-21 13:26:56 -05:00
|
|
|
Invalidate();
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DateTime ClampDate(DateTime date)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
if (date < MinimumDate)
|
|
|
|
|
return MinimumDate;
|
|
|
|
|
if (date > MaximumDate)
|
|
|
|
|
return MaximumDate;
|
2025-12-19 09:30:16 +00:00
|
|
|
return date;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
private void DrawCalendarOverlay(SKCanvas canvas)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
if (_isOpen)
|
|
|
|
|
{
|
|
|
|
|
DrawCalendar(canvas, ScreenBounds);
|
|
|
|
|
}
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawPickerButton(SKCanvas canvas, SKRect bounds)
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
float cornerRadius = (float)CornerRadius;
|
|
|
|
|
float fontSize = (float)FontSize;
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
using var bgPaint = new SKPaint
|
|
|
|
|
{
|
2026-01-17 03:36:37 +00:00
|
|
|
Color = IsEnabled ? GetEffectiveBackgroundColor() : SkiaTheme.Gray100SK,
|
2025-12-19 09:30:16 +00:00
|
|
|
Style = SKPaintStyle.Fill,
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
2026-01-16 05:14:04 +00:00
|
|
|
canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), bgPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
SKColor borderColor = IsFocused ? ToSKColor(SelectedDayColor) : ToSKColor(BorderColor);
|
2025-12-19 09:30:16 +00:00
|
|
|
using var borderPaint = new SKPaint
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
Color = borderColor,
|
2025-12-19 09:30:16 +00:00
|
|
|
Style = SKPaintStyle.Stroke,
|
|
|
|
|
StrokeWidth = IsFocused ? 2 : 1,
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
2026-01-16 05:14:04 +00:00
|
|
|
canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), borderPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
SKColor textColor = ToSKColor(TextColor);
|
2026-01-16 05:36:22 +00:00
|
|
|
|
|
|
|
|
// Get typeface based on FontFamily and FontAttributes
|
|
|
|
|
SKTypeface typeface = SKTypeface.Default;
|
|
|
|
|
if (!string.IsNullOrEmpty(FontFamily))
|
|
|
|
|
{
|
|
|
|
|
var style = FontAttributes switch
|
|
|
|
|
{
|
|
|
|
|
FontAttributes.Bold => SKFontStyle.Bold,
|
|
|
|
|
FontAttributes.Italic => SKFontStyle.Italic,
|
|
|
|
|
FontAttributes.Bold | FontAttributes.Italic => SKFontStyle.BoldItalic,
|
|
|
|
|
_ => SKFontStyle.Normal
|
|
|
|
|
};
|
|
|
|
|
typeface = SKTypeface.FromFamilyName(FontFamily, style) ?? SKTypeface.Default;
|
|
|
|
|
}
|
|
|
|
|
else if (FontAttributes != FontAttributes.None)
|
|
|
|
|
{
|
|
|
|
|
var style = FontAttributes switch
|
|
|
|
|
{
|
|
|
|
|
FontAttributes.Bold => SKFontStyle.Bold,
|
|
|
|
|
FontAttributes.Italic => SKFontStyle.Italic,
|
|
|
|
|
FontAttributes.Bold | FontAttributes.Italic => SKFontStyle.BoldItalic,
|
|
|
|
|
_ => SKFontStyle.Normal
|
|
|
|
|
};
|
|
|
|
|
typeface = SKTypeface.FromFamilyName(null, style) ?? SKTypeface.Default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using var font = new SKFont(typeface, fontSize, 1f, 0f);
|
2025-12-19 09:30:16 +00:00
|
|
|
using var textPaint = new SKPaint(font)
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
Color = IsEnabled ? textColor : textColor.WithAlpha(128),
|
2025-12-19 09:30:16 +00:00
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
string dateText = Date.ToString(Format);
|
|
|
|
|
SKRect textBounds = default;
|
2025-12-19 09:30:16 +00:00
|
|
|
textPaint.MeasureText(dateText, ref textBounds);
|
2026-01-01 13:51:12 -05:00
|
|
|
canvas.DrawText(dateText, bounds.Left + 12f, bounds.MidY - textBounds.MidY, textPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
DrawCalendarIcon(canvas, new SKRect(bounds.Right - 36f, bounds.MidY - 10f, bounds.Right - 12f, bounds.MidY + 10f));
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawCalendarIcon(SKCanvas canvas, SKRect bounds)
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
SKColor textColor = ToSKColor(TextColor);
|
2025-12-19 09:30:16 +00:00
|
|
|
using var paint = new SKPaint
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
Color = IsEnabled ? textColor : textColor.WithAlpha(128),
|
2025-12-19 09:30:16 +00:00
|
|
|
Style = SKPaintStyle.Stroke,
|
|
|
|
|
StrokeWidth = 1.5f,
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
SKRect calRect = new SKRect(bounds.Left, bounds.Top + 3f, bounds.Right, bounds.Bottom);
|
|
|
|
|
canvas.DrawRoundRect(new SKRoundRect(calRect, 2f), paint);
|
|
|
|
|
canvas.DrawLine(bounds.Left + 5f, bounds.Top, bounds.Left + 5f, bounds.Top + 5f, paint);
|
|
|
|
|
canvas.DrawLine(bounds.Right - 5f, bounds.Top, bounds.Right - 5f, bounds.Top + 5f, paint);
|
|
|
|
|
canvas.DrawLine(bounds.Left, bounds.Top + 8f, bounds.Right, bounds.Top + 8f, paint);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
|
|
|
|
paint.Style = SKPaintStyle.Fill;
|
2026-01-01 13:51:12 -05:00
|
|
|
for (int i = 0; i < 2; i++)
|
|
|
|
|
{
|
|
|
|
|
for (int j = 0; j < 3; j++)
|
|
|
|
|
{
|
|
|
|
|
canvas.DrawCircle(bounds.Left + 4f + j * 6, bounds.Top + 12f + i * 4, 1f, paint);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawCalendar(SKCanvas canvas, SKRect bounds)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
SKRect calendarRect = GetCalendarRect(bounds);
|
2026-01-16 05:14:04 +00:00
|
|
|
float cornerRadius = (float)CornerRadius;
|
2025-12-19 09:30:16 +00:00
|
|
|
|
|
|
|
|
using var shadowPaint = new SKPaint
|
|
|
|
|
{
|
2026-01-17 03:36:37 +00:00
|
|
|
Color = SkiaTheme.Shadow25SK,
|
2026-01-01 13:51:12 -05:00
|
|
|
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 4f),
|
2025-12-19 09:30:16 +00:00
|
|
|
Style = SKPaintStyle.Fill
|
|
|
|
|
};
|
2026-01-16 05:14:04 +00:00
|
|
|
canvas.DrawRoundRect(new SKRoundRect(new SKRect(calendarRect.Left + 2f, calendarRect.Top + 2f, calendarRect.Right + 2f, calendarRect.Bottom + 2f), cornerRadius), shadowPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
using var bgPaint = new SKPaint
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
Color = ToSKColor(CalendarBackgroundColor),
|
2026-01-01 13:51:12 -05:00
|
|
|
Style = SKPaintStyle.Fill,
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
2026-01-16 05:14:04 +00:00
|
|
|
canvas.DrawRoundRect(new SKRoundRect(calendarRect, cornerRadius), bgPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
using var borderPaint = new SKPaint
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
Color = ToSKColor(BorderColor),
|
2026-01-01 13:51:12 -05:00
|
|
|
Style = SKPaintStyle.Stroke,
|
|
|
|
|
StrokeWidth = 1f,
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
2026-01-16 05:14:04 +00:00
|
|
|
canvas.DrawRoundRect(new SKRoundRect(calendarRect, cornerRadius), borderPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
DrawCalendarHeader(canvas, new SKRect(calendarRect.Left, calendarRect.Top, calendarRect.Right, calendarRect.Top + 48f));
|
|
|
|
|
DrawWeekdayHeaders(canvas, new SKRect(calendarRect.Left, calendarRect.Top + 48f, calendarRect.Right, calendarRect.Top + 48f + 30f));
|
|
|
|
|
DrawDays(canvas, new SKRect(calendarRect.Left, calendarRect.Top + 48f + 30f, calendarRect.Right, calendarRect.Bottom));
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawCalendarHeader(SKCanvas canvas, SKRect bounds)
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
float cornerRadius = (float)CornerRadius;
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
using var headerPaint = new SKPaint
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
Color = ToSKColor(HeaderColor),
|
2026-01-01 13:51:12 -05:00
|
|
|
Style = SKPaintStyle.Fill
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
canvas.Save();
|
2026-01-16 05:14:04 +00:00
|
|
|
canvas.ClipRoundRect(new SKRoundRect(new SKRect(bounds.Left, bounds.Top, bounds.Right, bounds.Top + cornerRadius * 2f), cornerRadius), SKClipOperation.Intersect, false);
|
2025-12-21 13:26:56 -05:00
|
|
|
canvas.DrawRect(bounds, headerPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
canvas.Restore();
|
2026-01-16 05:14:04 +00:00
|
|
|
canvas.DrawRect(new SKRect(bounds.Left, bounds.Top + cornerRadius, bounds.Right, bounds.Bottom), headerPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
using var font = new SKFont(SKTypeface.Default, 16f, 1f, 0f);
|
|
|
|
|
using var textPaint = new SKPaint(font)
|
|
|
|
|
{
|
2026-01-17 03:36:37 +00:00
|
|
|
Color = SkiaTheme.BackgroundWhiteSK,
|
2026-01-01 13:51:12 -05:00
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
string monthYear = _displayMonth.ToString("MMMM yyyy");
|
|
|
|
|
SKRect textBounds = default;
|
2025-12-19 09:30:16 +00:00
|
|
|
textPaint.MeasureText(monthYear, ref textBounds);
|
|
|
|
|
canvas.DrawText(monthYear, bounds.MidX - textBounds.MidX, bounds.MidY - textBounds.MidY, textPaint);
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
using var arrowPaint = new SKPaint
|
|
|
|
|
{
|
2026-01-17 03:36:37 +00:00
|
|
|
Color = SkiaTheme.BackgroundWhiteSK,
|
2026-01-01 13:51:12 -05:00
|
|
|
Style = SKPaintStyle.Stroke,
|
|
|
|
|
StrokeWidth = 2f,
|
|
|
|
|
IsAntialias = true,
|
|
|
|
|
StrokeCap = SKStrokeCap.Round
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
using var leftPath = new SKPath();
|
2026-01-01 13:51:12 -05:00
|
|
|
leftPath.MoveTo(bounds.Left + 26f, bounds.MidY - 6f);
|
|
|
|
|
leftPath.LineTo(bounds.Left + 20f, bounds.MidY);
|
|
|
|
|
leftPath.LineTo(bounds.Left + 26f, bounds.MidY + 6f);
|
2025-12-19 09:30:16 +00:00
|
|
|
canvas.DrawPath(leftPath, arrowPaint);
|
|
|
|
|
|
|
|
|
|
using var rightPath = new SKPath();
|
2026-01-01 13:51:12 -05:00
|
|
|
rightPath.MoveTo(bounds.Right - 26f, bounds.MidY - 6f);
|
|
|
|
|
rightPath.LineTo(bounds.Right - 20f, bounds.MidY);
|
|
|
|
|
rightPath.LineTo(bounds.Right - 26f, bounds.MidY + 6f);
|
2025-12-19 09:30:16 +00:00
|
|
|
canvas.DrawPath(rightPath, arrowPaint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawWeekdayHeaders(SKCanvas canvas, SKRect bounds)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
string[] dayNames = new string[] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
|
|
|
|
|
float cellWidth = bounds.Width / 7f;
|
|
|
|
|
|
|
|
|
|
using var font = new SKFont(SKTypeface.Default, 12f, 1f, 0f);
|
|
|
|
|
using var paint = new SKPaint(font)
|
|
|
|
|
{
|
2026-01-17 03:36:37 +00:00
|
|
|
Color = SkiaTheme.TextPlaceholderSK,
|
2026-01-01 13:51:12 -05:00
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
for (int i = 0; i < 7; i++)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
SKRect textBounds = default;
|
2025-12-19 09:30:16 +00:00
|
|
|
paint.MeasureText(dayNames[i], ref textBounds);
|
2026-01-01 13:51:12 -05:00
|
|
|
canvas.DrawText(dayNames[i], bounds.Left + i * cellWidth + cellWidth / 2f - textBounds.MidX, bounds.MidY - textBounds.MidY, paint);
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DrawDays(SKCanvas canvas, SKRect bounds)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
DateTime firstDay = new DateTime(_displayMonth.Year, _displayMonth.Month, 1);
|
|
|
|
|
int daysInMonth = DateTime.DaysInMonth(_displayMonth.Year, _displayMonth.Month);
|
|
|
|
|
int startDayOfWeek = (int)firstDay.DayOfWeek;
|
|
|
|
|
float cellWidth = bounds.Width / 7f;
|
|
|
|
|
float cellHeight = (bounds.Height - 10f) / 6f;
|
|
|
|
|
|
|
|
|
|
using var font = new SKFont(SKTypeface.Default, 14f, 1f, 0f);
|
|
|
|
|
using var textPaint = new SKPaint(font)
|
|
|
|
|
{
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
|
|
|
|
using var bgPaint = new SKPaint
|
|
|
|
|
{
|
|
|
|
|
Style = SKPaintStyle.Fill,
|
|
|
|
|
IsAntialias = true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
DateTime today = DateTime.Today;
|
|
|
|
|
SKRect cellRect = default;
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-16 05:14:04 +00:00
|
|
|
SKColor textColor = ToSKColor(TextColor);
|
|
|
|
|
SKColor selectedDayColor = ToSKColor(SelectedDayColor);
|
|
|
|
|
SKColor todayColor = ToSKColor(TodayColor);
|
|
|
|
|
SKColor disabledDayColor = ToSKColor(DisabledDayColor);
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
for (int day = 1; day <= daysInMonth; day++)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
DateTime dayDate = new DateTime(_displayMonth.Year, _displayMonth.Month, day);
|
|
|
|
|
int cellIndex = startDayOfWeek + day - 1;
|
|
|
|
|
int row = cellIndex / 7;
|
|
|
|
|
int col = cellIndex % 7;
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
cellRect = new SKRect(
|
|
|
|
|
bounds.Left + col * cellWidth + 2f,
|
|
|
|
|
bounds.Top + row * cellHeight + 2f,
|
|
|
|
|
bounds.Left + (col + 1) * cellWidth - 2f,
|
|
|
|
|
bounds.Top + (row + 1) * cellHeight - 2f);
|
|
|
|
|
|
|
|
|
|
bool isSelected = dayDate.Date == Date.Date;
|
|
|
|
|
bool isToday = dayDate.Date == today;
|
|
|
|
|
bool isDisabled = dayDate < MinimumDate || dayDate > MaximumDate;
|
2025-12-19 09:30:16 +00:00
|
|
|
|
|
|
|
|
if (isSelected)
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
bgPaint.Color = selectedDayColor;
|
2026-01-01 13:51:12 -05:00
|
|
|
canvas.DrawCircle(cellRect.MidX, cellRect.MidY, Math.Min(cellRect.Width, cellRect.Height) / 2f - 2f, bgPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
else if (isToday)
|
|
|
|
|
{
|
2026-01-16 05:14:04 +00:00
|
|
|
bgPaint.Color = todayColor;
|
2026-01-01 13:51:12 -05:00
|
|
|
canvas.DrawCircle(cellRect.MidX, cellRect.MidY, Math.Min(cellRect.Width, cellRect.Height) / 2f - 2f, bgPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-17 03:36:37 +00:00
|
|
|
textPaint.Color = isSelected ? SkiaTheme.BackgroundWhiteSK : (isDisabled ? disabledDayColor : textColor);
|
2026-01-01 13:51:12 -05:00
|
|
|
string dayText = day.ToString();
|
|
|
|
|
SKRect dayTextBounds = default;
|
|
|
|
|
textPaint.MeasureText(dayText, ref dayTextBounds);
|
|
|
|
|
canvas.DrawText(dayText, cellRect.MidX - dayTextBounds.MidX, cellRect.MidY - dayTextBounds.MidY, textPaint);
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Overrides
|
|
|
|
|
|
|
|
|
|
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
|
|
|
|
{
|
|
|
|
|
DrawPickerButton(canvas, bounds);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
public override void OnPointerPressed(PointerEventArgs e)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
if (!IsEnabled)
|
|
|
|
|
return;
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
if (IsOpen)
|
2025-12-19 09:30:16 +00:00
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
SKRect screenBounds = ScreenBounds;
|
|
|
|
|
SKRect calendarRect = GetCalendarRect(screenBounds);
|
2025-12-19 09:30:16 +00:00
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
SKRect headerRect = new SKRect(calendarRect.Left, calendarRect.Top, calendarRect.Right, calendarRect.Top + 48f);
|
2025-12-21 13:26:56 -05:00
|
|
|
if (headerRect.Contains(e.X, e.Y))
|
2025-12-19 09:30:16 +00:00
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
if (e.X < calendarRect.Left + 40f)
|
|
|
|
|
{
|
|
|
|
|
_displayMonth = _displayMonth.AddMonths(-1);
|
|
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
else if (e.X > calendarRect.Right - 40f)
|
|
|
|
|
{
|
|
|
|
|
_displayMonth = _displayMonth.AddMonths(1);
|
|
|
|
|
Invalidate();
|
|
|
|
|
}
|
2025-12-21 13:26:56 -05:00
|
|
|
return;
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-01 13:51:12 -05:00
|
|
|
float daysTop = calendarRect.Top + 48f + 30f;
|
|
|
|
|
SKRect daysRect = new SKRect(calendarRect.Left, daysTop, calendarRect.Right, calendarRect.Bottom);
|
2025-12-21 13:26:56 -05:00
|
|
|
if (daysRect.Contains(e.X, e.Y))
|
2025-12-19 09:30:16 +00:00
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
float cellWidth = 40f;
|
|
|
|
|
float cellHeight = 38.666668f;
|
|
|
|
|
int col = (int)((e.X - calendarRect.Left) / cellWidth);
|
|
|
|
|
int dayIndex = (int)((e.Y - daysTop) / cellHeight) * 7 + col - (int)new DateTime(_displayMonth.Year, _displayMonth.Month, 1).DayOfWeek + 1;
|
|
|
|
|
int daysInMonth = DateTime.DaysInMonth(_displayMonth.Year, _displayMonth.Month);
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
if (dayIndex >= 1 && dayIndex <= daysInMonth)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
DateTime selectedDate = new DateTime(_displayMonth.Year, _displayMonth.Month, dayIndex);
|
2025-12-21 13:26:56 -05:00
|
|
|
if (selectedDate >= MinimumDate && selectedDate <= MaximumDate)
|
2025-12-19 09:30:16 +00:00
|
|
|
{
|
|
|
|
|
Date = selectedDate;
|
2025-12-21 13:26:56 -05:00
|
|
|
IsOpen = false;
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-21 13:26:56 -05:00
|
|
|
return;
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
2025-12-21 13:26:56 -05:00
|
|
|
|
|
|
|
|
if (screenBounds.Contains(e.X, e.Y))
|
2025-12-19 09:30:16 +00:00
|
|
|
{
|
2025-12-21 13:26:56 -05:00
|
|
|
IsOpen = false;
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-01 13:51:12 -05:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
IsOpen = true;
|
|
|
|
|
}
|
2025-12-19 09:30:16 +00:00
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnKeyDown(KeyEventArgs e)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
if (!IsEnabled)
|
|
|
|
|
return;
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
switch (e.Key)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
case Key.Enter:
|
|
|
|
|
case Key.Space:
|
|
|
|
|
IsOpen = !IsOpen;
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
break;
|
|
|
|
|
case Key.Escape:
|
|
|
|
|
if (IsOpen)
|
|
|
|
|
{
|
|
|
|
|
IsOpen = false;
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Key.Left:
|
|
|
|
|
Date = Date.AddDays(-1.0);
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
break;
|
|
|
|
|
case Key.Right:
|
|
|
|
|
Date = Date.AddDays(1.0);
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
break;
|
|
|
|
|
case Key.Up:
|
|
|
|
|
Date = Date.AddDays(-7.0);
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
break;
|
|
|
|
|
case Key.Down:
|
|
|
|
|
Date = Date.AddDays(7.0);
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
break;
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
Invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
public override void OnFocusLost()
|
|
|
|
|
{
|
|
|
|
|
base.OnFocusLost();
|
|
|
|
|
if (IsOpen)
|
|
|
|
|
{
|
|
|
|
|
IsOpen = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
protected override SKSize MeasureOverride(SKSize availableSize)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
return new SKSize(availableSize.Width < float.MaxValue ? Math.Min(availableSize.Width, 200f) : 200f, 40f);
|
2025-12-21 13:26:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override bool HitTestPopupArea(float x, float y)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
SKRect screenBounds = ScreenBounds;
|
2025-12-21 13:26:56 -05:00
|
|
|
if (screenBounds.Contains(x, y))
|
2026-01-01 13:51:12 -05:00
|
|
|
{
|
2025-12-21 13:26:56 -05:00
|
|
|
return true;
|
2026-01-01 13:51:12 -05:00
|
|
|
}
|
2025-12-21 13:26:56 -05:00
|
|
|
if (_isOpen)
|
|
|
|
|
{
|
2026-01-01 13:51:12 -05:00
|
|
|
SKRect calendarRect = GetCalendarRect(screenBounds);
|
2025-12-21 13:26:56 -05:00
|
|
|
return calendarRect.Contains(x, y);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
2026-01-01 13:51:12 -05:00
|
|
|
|
|
|
|
|
#endregion
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|