Add AtomUI.Controls.CalendarPresenter.Calendar

This commit is contained in:
polarboy 2024-09-12 15:09:27 +08:00
parent 8b03543524
commit 5ecbaa81f1
34 changed files with 4014 additions and 72 deletions

View File

@ -23,9 +23,9 @@ public static class AtomUIExtensions
builder.AfterSetup(builder =>
{
var themeManager = ThemeManager.Current;
themeManager.CultureInfo = new CultureInfo(LanguageCode.en_US);
themeManager.LoadTheme(ThemeManager.DEFAULT_THEME_ID);
themeManager.SetActiveTheme(ThemeManager.DEFAULT_THEME_ID);
themeManager.CultureInfo = new CultureInfo(LanguageCode.en_US);
builder.Instance!.Styles.Add(themeManager);
});
return builder;

View File

@ -36,6 +36,9 @@ internal class Program
catch (Exception ex)
{
File.WriteAllText("error.log", ex.ToString());
#if DEBUG
throw;
#endif
}
}

View File

@ -3,5 +3,7 @@
<assembly fullname="AtomUI.Theme" preserve="All"/>
<assembly fullname="Avalonia.Base" preserve="All"/>
<assembly fullname="AtomUI.Controls" preserve="All"/>
<assembly fullname="AtomUI.Icon" preserve="All"/>
<assembly fullname="AtomUI.Icon.AntDesign" preserve="All"/>
<assembly fullname="Avalonia.Remote.Protocol" preserve="All"/>
</linker>

View File

@ -12,8 +12,5 @@
<atom:Calendar />
</desktop:ShowCaseItem>
<desktop:ShowCaseItem Description="The simplest usage for Calendar." Title="Range Calendar">
<atom:RangeCalendar FixedRangeStart="True" SelectedRangeStartDate="2024-9-10" SelectedRangeEndDate="2024-9-12"/>
</desktop:ShowCaseItem>
</desktop:ShowCasePanel>
</UserControl>

View File

@ -5,12 +5,20 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:desktop="clr-namespace:AtomUI.Demo.Desktop"
xmlns:calendarpresenter="clr-namespace:AtomUI.Controls.CalendarPresenter;assembly=AtomUI.Controls"
xmlns:atom="https://atomui.net"
mc:Ignorable="d">
<desktop:ShowCasePanel>
<desktop:ShowCaseItem
Title="Basic"
Description="Click TimePicker, and then we could select or input a time in panel.">
Description="Click DatePicker, and then we could select or input a date in panel.">
<atom:DatePicker Watermark="Select date"/>
</desktop:ShowCaseItem>
<desktop:ShowCaseItem
Title="Basic"
Description="Click DatePicker, and then we could select or input a date in panel.">
<calendarpresenter:Calendar />
</desktop:ShowCaseItem>
</desktop:ShowCasePanel>
</UserControl>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,206 @@
using System.Collections.ObjectModel;
using Avalonia.Threading;
namespace AtomUI.Controls.CalendarPresenter;
public sealed class CalendarBlackoutDatesCollection : ObservableCollection<CalendarDateRange>
{
/// <summary>
/// The Calendar whose dates this object represents.
/// </summary>
private readonly Calendar _owner;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Primitives.CalendarBlackoutDatesCollection" />
/// class.
/// </summary>
/// <param name="owner">
/// The <see cref="T:Avalonia.Controls.Calendar" /> whose dates
/// this object represents.
/// </param>
public CalendarBlackoutDatesCollection(Calendar owner)
{
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
}
/// <summary>
/// Adds all dates before <see cref="P:System.DateTime.Today" /> to the
/// collection.
/// </summary>
public void AddDatesInPast()
{
Add(new CalendarDateRange(DateTime.MinValue, DateTime.Today.AddDays(-1)));
}
/// <summary>
/// Returns a value that represents whether this collection contains the
/// specified date.
/// </summary>
/// <param name="date">The date to search for.</param>
/// <returns>
/// True if the collection contains the specified date; otherwise,
/// false.
/// </returns>
public bool Contains(DateTime date)
{
var count = Count;
for (var i = 0; i < count; i++)
{
if (DateTimeHelper.InRange(date, this[i]))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns a value that represents whether this collection contains the
/// specified range of dates.
/// </summary>
/// <param name="start">The start of the date range.</param>
/// <param name="end">The end of the date range.</param>
/// <returns>
/// True if all dates in the range are contained in the collection;
/// otherwise, false.
/// </returns>
public bool Contains(DateTime start, DateTime end)
{
DateTime rangeStart;
DateTime rangeEnd;
if (DateTime.Compare(end, start) > -1)
{
rangeStart = DateTimeHelper.DiscardTime(start);
rangeEnd = DateTimeHelper.DiscardTime(end);
}
else
{
rangeStart = DateTimeHelper.DiscardTime(end);
rangeEnd = DateTimeHelper.DiscardTime(start);
}
var count = Count;
for (var i = 0; i < count; i++)
{
var range = this[i];
if (DateTime.Compare(range.Start, rangeStart) == 0 && DateTime.Compare(range.End, rangeEnd) == 0)
{
return true;
}
}
return false;
}
/// <summary>
/// Returns a value that represents whether this collection contains any
/// date in the specified range.
/// </summary>
/// <param name="range">The range of dates to search for.</param>
/// <returns>
/// True if any date in the range is contained in the collection;
/// otherwise, false.
/// </returns>
public bool ContainsAny(CalendarDateRange range)
{
return this.Any(r => r.ContainsAny(range));
}
/// <summary>
/// Removes all items from the collection.
/// </summary>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void ClearItems()
{
EnsureValidThread();
base.ClearItems();
_owner.UpdateMonths();
}
/// <summary>
/// Inserts an item into the collection at the specified index.
/// </summary>
/// <param name="index">
/// The zero-based index at which item should be inserted.
/// </param>
/// <param name="item">The object to insert.</param>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void InsertItem(int index, CalendarDateRange item)
{
EnsureValidThread();
if (!IsValid(item))
{
throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid.");
}
base.InsertItem(index, item);
_owner.UpdateMonths();
}
/// <summary>
/// Removes the item at the specified index of the collection.
/// </summary>
/// <param name="index">
/// The zero-based index of the element to remove.
/// </param>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void RemoveItem(int index)
{
EnsureValidThread();
base.RemoveItem(index);
_owner.UpdateMonths();
}
/// <summary>
/// Replaces the element at the specified index.
/// </summary>
/// <param name="index">
/// The zero-based index of the element to replace.
/// </param>
/// <param name="item">
/// The new value for the element at the specified index.
/// </param>
/// <remarks>
/// This implementation raises the CollectionChanged event.
/// </remarks>
protected override void SetItem(int index, CalendarDateRange item)
{
EnsureValidThread();
if (!IsValid(item))
{
throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid.");
}
base.SetItem(index, item);
_owner.UpdateMonths();
}
private bool IsValid(CalendarDateRange item)
{
if (_owner.SelectedDate is not null &&
DateTimeHelper.InRange(_owner.SelectedDate.Value, item))
{
return false;
}
return true;
}
private static void EnsureValidThread()
{
Dispatcher.UIThread.VerifyAccess();
}
}

View File

@ -0,0 +1,198 @@
using AtomUI.Media;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using AvaloniaButton = Avalonia.Controls.Button;
namespace AtomUI.Controls.CalendarPresenter;
/// <summary>
/// Represents a button on a
/// <see cref="T:Avalonia.Controls.Calendar" />.
/// </summary>
[PseudoClasses(StdPseudoClass.Selected, StdPseudoClass.InActive, BtnFocusedPC)]
internal sealed class CalendarButton : AvaloniaButton
{
internal const string BtnFocusedPC = ":btnfocused";
/// <summary>
/// Gets or sets the Calendar associated with this button.
/// </summary>
internal Calendar? Owner { get; set; }
/// <summary>
/// A value indicating whether the button is focused.
/// </summary>
private bool _isCalendarButtonFocused;
/// <summary>
/// A value indicating whether the button is inactive.
/// </summary>
private bool _isInactive;
/// <summary>
/// A value indicating whether the button is selected.
/// </summary>
private bool _isSelected;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Primitives.CalendarButton" />
/// class.
/// </summary>
public CalendarButton()
{
SetCurrentValue(ContentProperty, DateTimeHelper.GetCurrentDateFormat().AbbreviatedMonthNames[0]);
}
/// <summary>
/// Gets or sets a value indicating whether the button is focused.
/// </summary>
internal bool IsCalendarButtonFocused
{
get => _isCalendarButtonFocused;
set
{
if (_isCalendarButtonFocused != value)
{
_isCalendarButtonFocused = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the button is inactive.
/// </summary>
internal bool IsInactive
{
get => _isInactive;
set
{
if (_isInactive != value)
{
_isInactive = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the button is selected.
/// </summary>
internal bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Builds the visual tree for the
/// <see cref="T:System.Windows.Controls.Primitives.CalendarButton" />
/// when a new template is applied.
/// </summary>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
SetPseudoClasses();
if (Transitions is null)
{
Transitions = new Transitions
{
AnimationUtils.CreateTransition<SolidColorBrushTransition>(BackgroundProperty,
GlobalTokenResourceKey.MotionDurationFast)
};
}
}
/// <summary>
/// Sets PseudoClasses based on current state.
/// </summary>
private void SetPseudoClasses()
{
PseudoClasses.Set(StdPseudoClass.Selected, IsSelected);
PseudoClasses.Set(StdPseudoClass.InActive, IsInactive);
PseudoClasses.Set(BtnFocusedPC, IsCalendarButtonFocused && IsEnabled);
}
/// <summary>
/// Occurs when the left mouse button is pressed (or when the tip of the
/// stylus touches the tablet PC) while the mouse pointer is over a
/// UIElement.
/// </summary>
public event EventHandler<PointerPressedEventArgs>? CalendarLeftMouseButtonDown;
/// <summary>
/// Occurs when the left mouse button is released (or the tip of the
/// stylus is removed from the tablet PC) while the mouse (or the
/// stylus) is over a UIElement (or while a UIElement holds mouse
/// capture).
/// </summary>
public event EventHandler<PointerReleasedEventArgs>? CalendarLeftMouseButtonUp;
/// <summary>
/// Provides class handling for the MouseLeftButtonDown event that
/// occurs when the left mouse button is pressed while the mouse pointer
/// is over this control.
/// </summary>
/// <param name="e">The event data. </param>
/// <exception cref="System.ArgumentNullException">
/// e is a null reference (Nothing in Visual Basic).
/// </exception>
/// <remarks>
/// This method marks the MouseLeftButtonDown event as handled by
/// setting the MouseButtonEventArgs.Handled property of the event data
/// to true when the button is enabled and its ClickMode is not set to
/// Hover. Since this method marks the MouseLeftButtonDown event as
/// handled in some situations, you should use the Click event instead
/// to detect a button click.
/// </remarks>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
CalendarLeftMouseButtonDown?.Invoke(this, e);
}
}
/// <summary>
/// Provides handling for the MouseLeftButtonUp event that occurs when
/// the left mouse button is released while the mouse pointer is over
/// this control.
/// </summary>
/// <param name="e">The event data.</param>
/// <exception cref="System.ArgumentNullException">
/// e is a null reference (Nothing in Visual Basic).
/// </exception>
/// <remarks>
/// This method marks the MouseLeftButtonUp event as handled by setting
/// the MouseButtonEventArgs.Handled property of the event data to true
/// when the button is enabled and its ClickMode is not set to Hover.
/// Since this method marks the MouseLeftButtonUp event as handled in
/// some situations, you should use the Click event instead to detect a
/// button click.
/// </remarks>
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (e.InitialPressMouseButton == MouseButton.Left)
{
CalendarLeftMouseButtonUp?.Invoke(this, e);
}
}
}

View File

@ -0,0 +1,102 @@
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls.CalendarPresenter;
[ControlThemeProvider]
internal class CalendarButtonTheme : BaseControlTheme
{
private const string ContentPart = "PART_Content";
public CalendarButtonTheme()
: base(typeof(CalendarButton))
{
}
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<CalendarButton>((calendarButton, scope) =>
{
var contentPresenter = new ContentPresenter
{
Name = ContentPart
};
CreateTemplateParentBinding(contentPresenter, ContentPresenter.PaddingProperty,
TemplatedControl.PaddingProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ForegroundProperty,
TemplatedControl.ForegroundProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.BackgroundProperty,
TemplatedControl.BackgroundProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.CornerRadiusProperty,
TemplatedControl.CornerRadiusProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.BorderBrushProperty,
TemplatedControl.BorderBrushProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.BorderThicknessProperty,
TemplatedControl.BorderThicknessProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.FontSizeProperty,
TemplatedControl.FontSizeProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty,
ContentControl.ContentProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty,
ContentControl.ContentTemplateProperty);
CreateTemplateParentBinding(contentPresenter, Layoutable.HorizontalAlignmentProperty,
Layoutable.HorizontalAlignmentProperty);
CreateTemplateParentBinding(contentPresenter, Layoutable.VerticalAlignmentProperty,
Layoutable.VerticalAlignmentProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.HorizontalContentAlignmentProperty,
ContentControl.HorizontalContentAlignmentProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.VerticalContentAlignmentProperty,
ContentControl.VerticalContentAlignmentProperty);
return contentPresenter;
});
}
protected override void BuildStyles()
{
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(Avalonia.Controls.Button.ClickModeProperty, ClickMode.Release);
commonStyle.Add(InputElement.CursorProperty, new Cursor(StandardCursorType.Hand));
commonStyle.Add(TemplatedControl.BackgroundProperty, GlobalTokenResourceKey.ColorTransparent);
commonStyle.Add(TemplatedControl.ForegroundProperty, GlobalTokenResourceKey.ColorTextLabel);
commonStyle.Add(TemplatedControl.CornerRadiusProperty, GlobalTokenResourceKey.BorderRadiusSM);
commonStyle.Add(TemplatedControl.BorderBrushProperty, GlobalTokenResourceKey.ColorTransparent);
commonStyle.Add(Layoutable.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
commonStyle.Add(Layoutable.VerticalAlignmentProperty, VerticalAlignment.Stretch);
commonStyle.Add(ContentControl.HorizontalContentAlignmentProperty, HorizontalAlignment.Center);
commonStyle.Add(ContentControl.VerticalContentAlignmentProperty, VerticalAlignment.Center);
commonStyle.Add(TemplatedControl.FontSizeProperty, GlobalTokenResourceKey.FontSize);
commonStyle.Add(Layoutable.HeightProperty, DatePickerTokenResourceKey.CellHeight);
commonStyle.Add(Layoutable.MarginProperty, DatePickerTokenResourceKey.CellMargin);
var contentStyle = new Style(selector => selector.Nesting().Template().Name(ContentPart));
contentStyle.Add(ContentPresenter.LineHeightProperty, DatePickerTokenResourceKey.CellLineHeight);
commonStyle.Add(contentStyle);
var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver));
hoverStyle.Add(TemplatedControl.BackgroundProperty, DatePickerTokenResourceKey.CellHoverBg);
commonStyle.Add(hoverStyle);
var inactiveStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.InActive));
inactiveStyle.Add(TemplatedControl.ForegroundProperty, GlobalTokenResourceKey.ColorTextDisabled);
commonStyle.Add(inactiveStyle);
var selectedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Selected));
selectedStyle.Add(TemplatedControl.BackgroundProperty, GlobalTokenResourceKey.ColorPrimary);
selectedStyle.Add(TemplatedControl.ForegroundProperty, GlobalTokenResourceKey.ColorWhite);
selectedStyle.Add(TemplatedControl.BorderThicknessProperty, new Thickness(0));
commonStyle.Add(selectedStyle);
Add(commonStyle);
}
}

View File

@ -0,0 +1,281 @@
using System.Globalization;
using AtomUI.Media;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using AvaloniaButton = Avalonia.Controls.Button;
namespace AtomUI.Controls.CalendarPresenter;
[PseudoClasses(StdPseudoClass.Pressed,
StdPseudoClass.Disabled,
StdPseudoClass.Selected,
StdPseudoClass.InActive,
TodayPC,
BlackoutPC,
DayfocusedPC)]
internal sealed class CalendarDayButton : AvaloniaButton
{
internal const string TodayPC = ":today";
internal const string BlackoutPC = ":blackout";
internal const string DayfocusedPC = ":dayfocused";
/// <summary>
/// Gets or sets the Calendar associated with this button.
/// </summary>
internal Calendar? Owner { get; set; }
/// <summary>
/// Default content for the CalendarDayButton.
/// </summary>
private const int DefaultContent = 1;
private bool _ignoringMouseOverState;
private bool _isBlackout;
private bool _isCurrent;
private bool _isInactive;
private bool _isSelected;
private bool _isToday;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Primitives.CalendarDayButton" />
/// class.
/// </summary>
public CalendarDayButton()
{
//Focusable = false;
SetCurrentValue(ContentProperty, DefaultContent.ToString(CultureInfo.CurrentCulture));
}
internal int Index { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the button is the focused
/// element on the Calendar control.
/// </summary>
internal bool IsCurrent
{
get => _isCurrent;
set
{
if (_isCurrent != value)
{
_isCurrent = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether this is a blackout date.
/// </summary>
internal bool IsBlackout
{
get => _isBlackout;
set
{
if (_isBlackout != value)
{
_isBlackout = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether this button represents
/// today.
/// </summary>
internal bool IsToday
{
get => _isToday;
set
{
if (_isToday != value)
{
_isToday = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the button is inactive.
/// </summary>
internal bool IsInactive
{
get => _isInactive;
set
{
if (_isInactive != value)
{
_isInactive = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the button is selected.
/// </summary>
internal bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
SetPseudoClasses();
}
}
}
/// <summary>
/// Ensure the button is not in the MouseOver state.
/// </summary>
/// <remarks>
/// If a button is in the MouseOver state when a Popup is closed (as is
/// the case when you select a date in the DatePicker control), it will
/// continue to think it's in the mouse over state even when the Popup
/// opens again and it's not. This method is used to forcibly clear the
/// state by changing the CommonStates state group.
/// </remarks>
internal void IgnoreMouseOverState()
{
// TODO: Investigate whether this needs to be done by changing the
// state everytime we change any state, or if it can be done once
// to properly reset the control.
_ignoringMouseOverState = false;
// If the button thinks it's in the MouseOver state (which can
// happen when a Popup is closed before the button can change state)
// we will override the state so it shows up as normal.
if (IsPointerOver)
{
_ignoringMouseOverState = true;
SetPseudoClasses();
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
SetPseudoClasses();
if (Transitions is null)
{
Transitions = new Transitions
{
AnimationUtils.CreateTransition<SolidColorBrushTransition>(BackgroundProperty,
GlobalTokenResourceKey.MotionDurationFast)
};
}
}
private void SetPseudoClasses()
{
if (_ignoringMouseOverState)
{
PseudoClasses.Set(StdPseudoClass.Pressed, IsPressed);
PseudoClasses.Set(StdPseudoClass.Disabled, !IsEnabled);
}
PseudoClasses.Set(StdPseudoClass.Selected, IsSelected);
PseudoClasses.Set(StdPseudoClass.InActive, IsInactive);
PseudoClasses.Set(TodayPC, IsToday);
PseudoClasses.Set(BlackoutPC, IsBlackout);
PseudoClasses.Set(DayfocusedPC, IsCurrent && IsEnabled);
}
/// <summary>
/// Occurs when the left mouse button is pressed (or when the tip of the
/// stylus touches the tablet PC) while the mouse pointer is over a
/// UIElement.
/// </summary>
public event EventHandler<PointerPressedEventArgs>? CalendarDayButtonMouseDown;
/// <summary>
/// Occurs when the left mouse button is released (or the tip of the
/// stylus is removed from the tablet PC) while the mouse (or the
/// stylus) is over a UIElement (or while a UIElement holds mouse
/// capture).
/// </summary>
public event EventHandler<PointerReleasedEventArgs>? CalendarDayButtonMouseUp;
/// <summary>
/// Provides class handling for the MouseLeftButtonDown event that
/// occurs when the left mouse button is pressed while the mouse pointer
/// is over this control.
/// </summary>
/// <param name="e">The event data. </param>
/// <exception cref="System.ArgumentNullException">
/// e is a null reference (Nothing in Visual Basic).
/// </exception>
/// <remarks>
/// This method marks the MouseLeftButtonDown event as handled by
/// setting the MouseButtonEventArgs.Handled property of the event data
/// to true when the button is enabled and its ClickMode is not set to
/// Hover. Since this method marks the MouseLeftButtonDown event as
/// handled in some situations, you should use the Click event instead
/// to detect a button click.
/// </remarks>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
CalendarDayButtonMouseDown?.Invoke(this, e);
}
}
/// <summary>
/// Provides handling for the MouseLeftButtonUp event that occurs when
/// the left mouse button is released while the mouse pointer is over
/// this control.
/// </summary>
/// <param name="e">The event data.</param>
/// <exception cref="System.ArgumentNullException">
/// e is a null reference (Nothing in Visual Basic).
/// </exception>
/// <remarks>
/// This method marks the MouseLeftButtonUp event as handled by setting
/// the MouseButtonEventArgs.Handled property of the event data to true
/// when the button is enabled and its ClickMode is not set to Hover.
/// Since this method marks the MouseLeftButtonUp event as handled in
/// some situations, you should use the Click event instead to detect a
/// button click.
/// </remarks>
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (e.InitialPressMouseButton == MouseButton.Left)
{
CalendarDayButtonMouseUp?.Invoke(this, e);
}
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
TokenResourceBinder.CreateGlobalTokenBinding(this, BorderThicknessProperty,
GlobalTokenResourceKey.BorderThickness, BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this));
}
}

View File

@ -0,0 +1,109 @@
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls.CalendarPresenter;
[ControlThemeProvider]
internal class CalendarDayButtonTheme : BaseControlTheme
{
private const string ContentPart = "PART_Content";
public CalendarDayButtonTheme()
: base(typeof(CalendarDayButton))
{
}
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<CalendarDayButton>((calendarDayButton, scope) =>
{
var contentPresenter = new ContentPresenter
{
Name = ContentPart
};
CreateTemplateParentBinding(contentPresenter, ContentPresenter.PaddingProperty,
TemplatedControl.PaddingProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ForegroundProperty,
TemplatedControl.ForegroundProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.BackgroundProperty,
TemplatedControl.BackgroundProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.CornerRadiusProperty,
TemplatedControl.CornerRadiusProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.BorderBrushProperty,
TemplatedControl.BorderBrushProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.BorderThicknessProperty,
TemplatedControl.BorderThicknessProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.FontSizeProperty,
TemplatedControl.FontSizeProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty,
ContentControl.ContentProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty,
ContentControl.ContentTemplateProperty);
CreateTemplateParentBinding(contentPresenter, Layoutable.HorizontalAlignmentProperty,
Layoutable.HorizontalAlignmentProperty);
CreateTemplateParentBinding(contentPresenter, Layoutable.VerticalAlignmentProperty,
Layoutable.VerticalAlignmentProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.HorizontalContentAlignmentProperty,
ContentControl.HorizontalContentAlignmentProperty);
CreateTemplateParentBinding(contentPresenter, ContentPresenter.VerticalContentAlignmentProperty,
ContentControl.VerticalContentAlignmentProperty);
return contentPresenter;
});
}
protected override void BuildStyles()
{
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(Avalonia.Controls.Button.ClickModeProperty, ClickMode.Release);
commonStyle.Add(InputElement.CursorProperty, new Cursor(StandardCursorType.Hand));
commonStyle.Add(TemplatedControl.BackgroundProperty, GlobalTokenResourceKey.ColorTransparent);
commonStyle.Add(TemplatedControl.ForegroundProperty, GlobalTokenResourceKey.ColorTextLabel);
commonStyle.Add(TemplatedControl.CornerRadiusProperty, GlobalTokenResourceKey.BorderRadiusSM);
commonStyle.Add(TemplatedControl.BorderBrushProperty, GlobalTokenResourceKey.ColorTransparent);
commonStyle.Add(Layoutable.HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
commonStyle.Add(Layoutable.VerticalAlignmentProperty, VerticalAlignment.Stretch);
commonStyle.Add(ContentControl.HorizontalContentAlignmentProperty, HorizontalAlignment.Center);
commonStyle.Add(ContentControl.VerticalContentAlignmentProperty, VerticalAlignment.Center);
commonStyle.Add(TemplatedControl.FontSizeProperty, GlobalTokenResourceKey.FontSize);
commonStyle.Add(Layoutable.WidthProperty, DatePickerTokenResourceKey.CellWidth);
commonStyle.Add(Layoutable.HeightProperty, DatePickerTokenResourceKey.CellHeight);
commonStyle.Add(Layoutable.MarginProperty, DatePickerTokenResourceKey.CellMargin);
var contentStyle = new Style(selector => selector.Nesting().Template().Name(ContentPart));
contentStyle.Add(ContentPresenter.LineHeightProperty, DatePickerTokenResourceKey.CellLineHeight);
commonStyle.Add(contentStyle);
var hoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver));
hoverStyle.Add(TemplatedControl.BackgroundProperty, DatePickerTokenResourceKey.CellHoverBg);
commonStyle.Add(hoverStyle);
var inactiveStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.InActive));
inactiveStyle.Add(TemplatedControl.ForegroundProperty, GlobalTokenResourceKey.ColorTextDisabled);
commonStyle.Add(inactiveStyle);
var todayStyle = new Style(selector => selector.Nesting().Class(BaseCalendarDayButton.TodayPC));
todayStyle.Add(TemplatedControl.BorderBrushProperty, GlobalTokenResourceKey.ColorPrimary);
commonStyle.Add(todayStyle);
var selectedStyle = new Style(selector =>
selector.Nesting().Class(StdPseudoClass.Selected)
.Not(selector1 => selector1.Nesting().Class(StdPseudoClass.InActive)));
selectedStyle.Add(TemplatedControl.BackgroundProperty, GlobalTokenResourceKey.ColorPrimary);
selectedStyle.Add(TemplatedControl.ForegroundProperty, GlobalTokenResourceKey.ColorWhite);
selectedStyle.Add(TemplatedControl.BorderThicknessProperty, new Thickness(0));
commonStyle.Add(selectedStyle);
Add(commonStyle);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,319 @@
using AtomUI.Data;
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls.CalendarPresenter;
[ControlThemeProvider]
internal class CalendarItemTheme : BaseControlTheme
{
public const string ItemFramePart = "PART_ItemFrame";
public const string ItemRootLayoutPart = "PART_ItemRootLayout";
public const string MonthViewLayoutPart = "PART_MonthViewLayout";
public const string MonthViewPart = "PART_MonthView";
public const string YearViewPart = "PART_YearView";
public const string HeaderLayoutPart = "PART_HeaderLayout";
public const string PreviousButtonPart = "PART_PreviousButton";
public const string PreviousMonthButtonPart = "PART_PreviousMonthButton";
public const string HeaderButtonPart = "PART_HeaderButton";
public const string NextMonthButtonPart = "PART_NextMonthButton";
public const string NextButtonPart = "PART_NextButton";
public CalendarItemTheme()
: base(typeof(CalendarItem))
{
}
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<CalendarItem>((calendarItem, scope) =>
{
var frame = new Border
{
Name = ItemFramePart
};
CreateTemplateParentBinding(frame, Border.MinWidthProperty, CalendarItem.MinWidthProperty);
CreateTemplateParentBinding(frame, Border.MinHeightProperty, CalendarItem.MinHeightProperty);
var rootLayout = new DockPanel
{
Name = ItemRootLayoutPart,
LastChildFill = true
};
BuildHeader(rootLayout, scope);
BuildContentView(rootLayout, scope);
BuildDayTitleTemplate(calendarItem);
frame.Child = rootLayout;
return frame;
});
}
private void BuildDayTitleTemplate(CalendarItem calendarItem)
{
calendarItem.DayTitleTemplate = new DayTitleTemplate();
}
protected virtual void BuildHeader(DockPanel layout, INameScope scope)
{
var headerLayout = new UniformGrid
{
Name = HeaderLayoutPart,
Columns = 1
};
headerLayout.RegisterInNameScope(scope);
BuildHeaderItem(headerLayout, scope);
DockPanel.SetDock(headerLayout, Dock.Top);
layout.Children.Add(headerLayout);
}
private void BuildHeaderItem(UniformGrid layout, INameScope scope)
{
var headerLayout = new Grid
{
ColumnDefinitions = new ColumnDefinitions
{
new(GridLength.Auto),
new(GridLength.Auto),
new(GridLength.Star),
new(GridLength.Auto),
new(GridLength.Auto)
}
};
var previousButton = BuildPreviousButton(PreviousButtonPart);
previousButton.RegisterInNameScope(scope);
Grid.SetColumn(previousButton, 0);
headerLayout.Children.Add(previousButton);
var previousMonthButton = BuildPreviousMonthButton(PreviousMonthButtonPart);
CreateTemplateParentBinding(previousMonthButton, Visual.IsVisibleProperty,
CalendarItem.IsMonthViewModeProperty);
previousMonthButton.RegisterInNameScope(scope);
Grid.SetColumn(previousMonthButton, 1);
headerLayout.Children.Add(previousMonthButton);
var headerButton = new HeadTextButton
{
Name = HeaderButtonPart
};
Grid.SetColumn(headerButton, 2);
headerButton.RegisterInNameScope(scope);
headerLayout.Children.Add(headerButton);
var nextMonthButton = BuildNextMonthButton(NextMonthButtonPart);
CreateTemplateParentBinding(nextMonthButton, Visual.IsVisibleProperty, CalendarItem.IsMonthViewModeProperty);
nextMonthButton.IsVisible = false;
nextMonthButton.RegisterInNameScope(scope);
Grid.SetColumn(nextMonthButton, 3);
headerLayout.Children.Add(nextMonthButton);
var nextButton = BuildNextButton(NextButtonPart);
nextButton.RegisterInNameScope(scope);
Grid.SetColumn(nextButton, 4);
headerLayout.Children.Add(nextButton);
layout.Children.Add(headerLayout);
}
protected virtual IconButton BuildPreviousButton(string name)
{
var previousButtonIcon = new PathIcon
{
Kind = "DoubleLeftOutlined"
};
TokenResourceBinder.CreateGlobalTokenBinding(previousButtonIcon, PathIcon.NormalFilledBrushProperty,
GlobalTokenResourceKey.ColorTextDescription);
TokenResourceBinder.CreateGlobalTokenBinding(previousButtonIcon, PathIcon.ActiveFilledBrushProperty,
GlobalTokenResourceKey.ColorText);
TokenResourceBinder.CreateGlobalTokenBinding(previousButtonIcon, PathIcon.SelectedFilledBrushProperty,
GlobalTokenResourceKey.ColorText);
var previousButton = new IconButton
{
Name = name,
Icon = previousButtonIcon
};
TokenResourceBinder.CreateGlobalTokenBinding(previousButton, IconButton.IconWidthProperty,
GlobalTokenResourceKey.IconSizeSM);
TokenResourceBinder.CreateGlobalTokenBinding(previousButton, IconButton.IconHeightProperty,
GlobalTokenResourceKey.IconSizeSM);
return previousButton;
}
protected virtual IconButton BuildPreviousMonthButton(string name)
{
var previousMonthButtonIcon = new PathIcon
{
Kind = "LeftOutlined"
};
TokenResourceBinder.CreateGlobalTokenBinding(previousMonthButtonIcon, PathIcon.NormalFilledBrushProperty,
GlobalTokenResourceKey.ColorTextDescription);
TokenResourceBinder.CreateGlobalTokenBinding(previousMonthButtonIcon, PathIcon.ActiveFilledBrushProperty,
GlobalTokenResourceKey.ColorText);
TokenResourceBinder.CreateGlobalTokenBinding(previousMonthButtonIcon, PathIcon.SelectedFilledBrushProperty,
GlobalTokenResourceKey.ColorText);
var previousMonthButton = new IconButton
{
Name = name,
Icon = previousMonthButtonIcon
};
TokenResourceBinder.CreateGlobalTokenBinding(previousMonthButton, IconButton.IconWidthProperty,
GlobalTokenResourceKey.IconSizeSM);
TokenResourceBinder.CreateGlobalTokenBinding(previousMonthButton, IconButton.IconHeightProperty,
GlobalTokenResourceKey.IconSizeSM);
return previousMonthButton;
}
protected virtual IconButton BuildNextButton(string name)
{
var nextButtonIcon = new PathIcon
{
Kind = "DoubleRightOutlined"
};
TokenResourceBinder.CreateGlobalTokenBinding(nextButtonIcon, PathIcon.NormalFilledBrushProperty,
GlobalTokenResourceKey.ColorTextDescription);
TokenResourceBinder.CreateGlobalTokenBinding(nextButtonIcon, PathIcon.ActiveFilledBrushProperty,
GlobalTokenResourceKey.ColorText);
TokenResourceBinder.CreateGlobalTokenBinding(nextButtonIcon, PathIcon.SelectedFilledBrushProperty,
GlobalTokenResourceKey.ColorText);
var nextButton = new IconButton
{
Name = name,
Icon = nextButtonIcon
};
TokenResourceBinder.CreateGlobalTokenBinding(nextButton, IconButton.IconWidthProperty,
GlobalTokenResourceKey.IconSizeSM);
TokenResourceBinder.CreateGlobalTokenBinding(nextButton, IconButton.IconHeightProperty,
GlobalTokenResourceKey.IconSizeSM);
return nextButton;
}
protected virtual IconButton BuildNextMonthButton(string name)
{
var nextMonthButtonIcon = new PathIcon
{
Kind = "RightOutlined"
};
TokenResourceBinder.CreateGlobalTokenBinding(nextMonthButtonIcon, PathIcon.NormalFilledBrushProperty,
GlobalTokenResourceKey.ColorTextDescription);
TokenResourceBinder.CreateGlobalTokenBinding(nextMonthButtonIcon, PathIcon.ActiveFilledBrushProperty,
GlobalTokenResourceKey.ColorText);
TokenResourceBinder.CreateGlobalTokenBinding(nextMonthButtonIcon, PathIcon.SelectedFilledBrushProperty,
GlobalTokenResourceKey.ColorText);
var nextMonthButton = new IconButton
{
Name = name,
Icon = nextMonthButtonIcon
};
TokenResourceBinder.CreateGlobalTokenBinding(nextMonthButton, IconButton.IconWidthProperty,
GlobalTokenResourceKey.IconSizeSM);
TokenResourceBinder.CreateGlobalTokenBinding(nextMonthButton, IconButton.IconHeightProperty,
GlobalTokenResourceKey.IconSizeSM);
return nextMonthButton;
}
private void BuildContentView(DockPanel layout, INameScope scope)
{
var monthViewLayout = new UniformGrid
{
Name = MonthViewLayoutPart,
Columns = 1,
IsVisible = false
};
var monthView = BuildMonthViewItem(MonthViewPart);
monthView.RegisterInNameScope(scope);
monthViewLayout.Children.Add(monthView);
BindUtils.RelayBind(monthViewLayout, Visual.IsVisibleProperty, monthView, Visual.IsVisibleProperty);
monthViewLayout.RegisterInNameScope(scope);
layout.Children.Add(monthViewLayout);
var yearView = new Grid
{
Name = YearViewPart,
IsVisible = false,
RowDefinitions = new RowDefinitions
{
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star)
},
ColumnDefinitions = new ColumnDefinitions
{
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star)
}
};
yearView.RegisterInNameScope(scope);
layout.Children.Add(yearView);
}
private Grid BuildMonthViewItem(string name)
{
var dayTitleRowDef = new RowDefinition();
TokenResourceBinder.CreateTokenBinding(dayTitleRowDef, RowDefinition.HeightProperty,
DatePickerTokenResourceKey.DayTitleHeight);
var monthViewLayout = new Grid
{
Name = name,
HorizontalAlignment = HorizontalAlignment.Stretch,
IsVisible = false,
RowDefinitions = new RowDefinitions
{
dayTitleRowDef,
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star)
},
ColumnDefinitions = new ColumnDefinitions
{
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star),
new(GridLength.Star)
}
};
return monthViewLayout;
}
protected override void BuildStyles()
{
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(CalendarItem.MinHeightProperty, DatePickerTokenResourceKey.ItemPanelMinHeight);
commonStyle.Add(CalendarItem.MinWidthProperty, DatePickerTokenResourceKey.ItemPanelMinWidth);
var headerLayoutStyle = new Style(selector => selector.Nesting().Template().Name(HeaderLayoutPart));
headerLayoutStyle.Add(Layoutable.MarginProperty, DatePickerTokenResourceKey.HeaderMargin);
commonStyle.Add(headerLayoutStyle);
Add(commonStyle);
}
}

View File

@ -0,0 +1,71 @@
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls.CalendarPresenter;
[ControlThemeProvider]
internal class CalendarTheme : BaseControlTheme
{
public const string CalendarItemPart = "PART_CalendarItem";
public const string FramePart = "PART_Frame";
public CalendarTheme()
: this(typeof(Calendar))
{
}
public CalendarTheme(Type targetType) : base(targetType)
{
}
protected override IControlTemplate BuildControlTemplate()
{
return new FuncControlTemplate<Calendar>((calendar, scope) =>
{
var frame = new Border
{
Name = FramePart,
ClipToBounds = true
};
CreateTemplateParentBinding(frame, Border.BorderBrushProperty, Calendar.BorderBrushProperty);
CreateTemplateParentBinding(frame, Border.BorderThicknessProperty,
Calendar.BorderThicknessProperty);
CreateTemplateParentBinding(frame, Border.CornerRadiusProperty, Calendar.CornerRadiusProperty);
CreateTemplateParentBinding(frame, Border.BackgroundProperty, Calendar.BackgroundProperty);
CreateTemplateParentBinding(frame, Border.PaddingProperty, Calendar.PaddingProperty);
CreateTemplateParentBinding(frame, Border.MinWidthProperty, Calendar.MinWidthProperty);
CreateTemplateParentBinding(frame, Border.MinHeightProperty, Calendar.MinHeightProperty);
var calendarItem = new CalendarItem
{
Name = CalendarItemPart,
HorizontalAlignment = HorizontalAlignment.Stretch
};
calendarItem.RegisterInNameScope(scope);
frame.Child = calendarItem;
return frame;
});
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(Calendar.BorderBrushProperty, GlobalTokenResourceKey.ColorBorder);
commonStyle.Add(Calendar.CornerRadiusProperty, GlobalTokenResourceKey.BorderRadius);
commonStyle.Add(Calendar.BackgroundProperty, GlobalTokenResourceKey.ColorBgContainer);
commonStyle.Add(Calendar.PaddingProperty, DatePickerTokenResourceKey.PanelContentPadding);
commonStyle.Add(Calendar.HorizontalAlignmentProperty, HorizontalAlignment.Left);
commonStyle.Add(Calendar.VerticalAlignmentProperty, VerticalAlignment.Top);
Add(commonStyle);
}
}

View File

@ -13,7 +13,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
namespace AtomUI.Controls;
namespace AtomUI.Controls.CalendarPresenter;
public class RangeDateSelectedEventArgs : RoutedEventArgs
{

View File

@ -1,7 +1,7 @@
using System.Collections.ObjectModel;
using Avalonia.Threading;
namespace AtomUI.Controls;
namespace AtomUI.Controls.CalendarPresenter;
public sealed class RangeCalendarBlackoutDatesCollection : ObservableCollection<CalendarDateRange>
{

View File

@ -1,4 +1,4 @@
namespace AtomUI.Controls;
namespace AtomUI.Controls.CalendarPresenter;
internal sealed class RangeCalendarButton : BaseCalendarButton
{

View File

@ -1,4 +1,4 @@
namespace AtomUI.Controls;
namespace AtomUI.Controls.CalendarPresenter;
internal sealed class RangeCalendarDayButton : BaseCalendarDayButton
{

View File

@ -11,7 +11,7 @@ using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Media;
namespace AtomUI.Controls;
namespace AtomUI.Controls.CalendarPresenter;
[TemplatePart(RangeCalendarItemTheme.PrimaryHeaderButtonPart, typeof(HeadTextButton))]
[TemplatePart(RangeCalendarItemTheme.SecondaryHeaderButtonPart, typeof(HeadTextButton))]
@ -37,7 +37,7 @@ internal class RangeCalendarItem : TemplatedControl
protected readonly System.Globalization.Calendar _calendar = new GregorianCalendar();
#region
#region
public static readonly StyledProperty<IBrush?> HeaderBackgroundProperty =
RangeCalendar.HeaderBackgroundProperty.AddOwner<RangeCalendarItem>();
@ -1200,21 +1200,28 @@ internal class RangeCalendarItem : TemplatedControl
internal void HandleCellMouseLeftButtonDown(object? sender, PointerPressedEventArgs e)
{
if (Owner != null) {
if (!Owner.HasFocusInternal) {
if (Owner != null)
{
if (!Owner.HasFocusInternal)
{
Owner.Focus();
}
if (sender is RangeCalendarDayButton b) {
if (b.IsEnabled && !b.IsBlackout && b.DataContext is DateTime selectedDate) {
if (sender is RangeCalendarDayButton b)
{
if (b.IsEnabled && !b.IsBlackout && b.DataContext is DateTime selectedDate)
{
// Set the start or end of the selection
// range
if (Owner.FixedRangeStart)
{
Owner.SelectedRangeStartDate = selectedDate;
} else {
}
else
{
Owner.SelectedRangeEndDate = selectedDate;
}
Owner.UpdateHighlightDays();
if (Owner.SelectedRangeStartDate is not null && Owner.SelectedRangeEndDate is not null)
{
@ -1227,18 +1234,23 @@ internal class RangeCalendarItem : TemplatedControl
internal void HandleCellMouseLeftButtonUp(object? sender, PointerReleasedEventArgs e)
{
if (Owner != null) {
RangeCalendarDayButton? b = sender as RangeCalendarDayButton;
if (b != null && !b.IsBlackout) {
Owner.OnDayButtonMouseUp(e);
}
if (b != null && b.DataContext is DateTime selectedDate) {
// If the day is Disabled but a trailing day we should
// be able to switch months
if (b.IsInactive) {
Owner.OnDayClick(selectedDate);
}
}
if (Owner != null)
{
RangeCalendarDayButton? b = sender as RangeCalendarDayButton;
if (b != null && !b.IsBlackout)
{
Owner.OnDayButtonMouseUp(e);
}
if (b != null && b.DataContext is DateTime selectedDate)
{
// If the day is Disabled but a trailing day we should
// be able to switch months
if (b.IsInactive)
{
Owner.OnDayClick(selectedDate);
}
}
}
}
@ -1303,9 +1315,11 @@ internal class RangeCalendarItem : TemplatedControl
private void DetectRestoreHoverPosition(RawInputEventArgs args)
{
if (Owner is null) {
if (Owner is null)
{
return;
}
if (args is RawPointerEventArgs pointerEventArgs)
{
if (!IsPointerInMonthView(pointerEventArgs.Position))
@ -1335,10 +1349,13 @@ internal class RangeCalendarItem : TemplatedControl
{
return false;
}
if (Owner.DisplayMode == CalendarMode.Month)
{
return GetMonthViewRect(PrimaryMonthView!).Contains(position) || GetMonthViewRect(SecondaryMonthView!).Contains(position);
return GetMonthViewRect(PrimaryMonthView!).Contains(position) ||
GetMonthViewRect(SecondaryMonthView!).Contains(position);
}
return false;
}
@ -1347,6 +1364,7 @@ internal class RangeCalendarItem : TemplatedControl
var firstDay = (monthView.Children[7] as RangeCalendarDayButton)!;
var firstDayPos = firstDay.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(monthView)!) ?? default;
var monthViewPos = monthView.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(monthView)!) ?? default;
return new Rect(firstDayPos, new Size(monthView.Bounds.Width, monthViewPos.Y + monthView.Bounds.Height - firstDayPos.Y ));
return new Rect(firstDayPos,
new Size(monthView.Bounds.Width, monthViewPos.Y + monthView.Bounds.Height - firstDayPos.Y));
}
}

View File

@ -11,7 +11,7 @@ using Avalonia.Data.Converters;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
namespace AtomUI.Controls.CalendarPresenter;
[ControlThemeProvider]
internal class RangeCalendarItemTheme : BaseControlTheme

View File

@ -8,10 +8,10 @@ using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls;
namespace AtomUI.Controls.CalendarPresenter;
[ControlThemeProvider]
public class RangeCalendarTheme : BaseControlTheme
internal class RangeCalendarTheme : BaseControlTheme
{
public const string RootPart = "PART_Root";
public const string CalendarItemPart = "PART_CalendarItem";
@ -69,8 +69,6 @@ public class RangeCalendarTheme : BaseControlTheme
commonStyle.Add(TemplatedControl.CornerRadiusProperty, GlobalTokenResourceKey.BorderRadius);
commonStyle.Add(TemplatedControl.BackgroundProperty, GlobalTokenResourceKey.ColorBgContainer);
commonStyle.Add(TemplatedControl.PaddingProperty, DatePickerTokenResourceKey.PanelContentPadding);
commonStyle.Add(Layoutable.MinWidthProperty, DatePickerTokenResourceKey.ItemPanelMinWidth);
commonStyle.Add(Layoutable.MinHeightProperty, DatePickerTokenResourceKey.ItemPanelMinHeight);
Add(commonStyle);
}
}

View File

@ -0,0 +1,11 @@
using AtomUI.Controls.Internal;
namespace AtomUI.Controls;
public class DatePicker : InfoPickerInput
{
protected override Flyout CreatePickerFlyout()
{
return new Flyout();
}
}

View File

@ -0,0 +1,6 @@
namespace AtomUI.Controls;
public class DatePickerPresenter
{
}

View File

@ -0,0 +1,16 @@
using AtomUI.Theme;
using AtomUI.Theme.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class DatePickerPresenterTheme : BaseControlTheme
{
public DatePickerPresenterTheme() : this(typeof(DatePickerPresenter))
{
}
public DatePickerPresenterTheme(Type targetType) : base(targetType)
{
}
}

View File

@ -0,0 +1,30 @@
using AtomUI.Controls.Internal;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Styling;
namespace AtomUI.Controls;
[ControlThemeProvider]
internal class DatePickerTheme : InfoPickerInputTheme
{
public DatePickerTheme() : base(typeof(DatePicker))
{
}
protected override PathIcon BuildInfoIcon(InfoPickerInput infoPickerInput, INameScope scope)
{
return new PathIcon()
{
Kind = "CalendarOutlined"
};
}
protected override void BuildStyles()
{
base.BuildStyles();
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(DatePicker.MinWidthProperty, DatePickerTokenResourceKey.PickerInputMinWidth);
Add(commonStyle);
}
}

View File

@ -66,6 +66,11 @@ internal class DatePickerToken : AbstractControlDesignToken
/// </summary>
public double ItemPanelMinWidth { get; set; }
/// <summary>
/// 两个月日历项最小宽度
/// </summary>
public double RangeItemPanelMinWidth { get; set; }
/// <summary>
/// 日历项最小高度
/// </summary>
@ -101,28 +106,35 @@ internal class DatePickerToken : AbstractControlDesignToken
/// </summary>
public double RangeCalendarSpacing { get; set; }
/// <summary>
/// 日期选择器最小的宽度
/// </summary>
public double PickerInputMinWidth { get; set; }
internal override void CalculateFromAlias()
{
base.CalculateFromAlias();
var colorPrimary = _globalToken.ColorToken.ColorPrimaryToken.ColorPrimary;
CellHoverBg = _globalToken.ControlItemBgHover;
CellActiveWithRangeBg = _globalToken.ControlItemBgActive;
CellHoverWithRangeBg = colorPrimary.Lighten(35);
CellRangeBorderColor = colorPrimary.Lighten(20);
CellBgDisabled = _globalToken.ColorBgContainerDisabled;
CellWidth = _globalToken.HeightToken.ControlHeightSM;
CellHeight = _globalToken.HeightToken.ControlHeightSM;
TextHeight = _globalToken.HeightToken.ControlHeightLG;
WithoutTimeCellHeight = _globalToken.HeightToken.ControlHeightLG * 1.65;
CellMargin = new Thickness(_globalToken.MarginXXS);
PanelContentPadding = new Thickness(_globalToken.PaddingSM);
ItemPanelMinWidth = 260;
ItemPanelMinHeight = 290;
DayTitleHeight = new GridLength(40, GridUnitType.Pixel);
HeaderMargin = new Thickness(0, 0, 0, _globalToken.MarginXS);
CellLineHeight = CellHeight - 2; // 不知道为啥设置成一样,或者不设置文字有些靠下
RangeCalendarSpacing = 20;
CellHoverBg = _globalToken.ControlItemBgHover;
CellActiveWithRangeBg = _globalToken.ControlItemBgActive;
CellHoverWithRangeBg = colorPrimary.Lighten(35);
CellRangeBorderColor = colorPrimary.Lighten(20);
CellBgDisabled = _globalToken.ColorBgContainerDisabled;
CellWidth = _globalToken.HeightToken.ControlHeightSM;
CellHeight = _globalToken.HeightToken.ControlHeightSM;
TextHeight = _globalToken.HeightToken.ControlHeightLG;
WithoutTimeCellHeight = _globalToken.HeightToken.ControlHeightLG * 1.65;
CellMargin = new Thickness(_globalToken.MarginXXS);
PanelContentPadding = new Thickness(_globalToken.PaddingSM);
ItemPanelMinWidth = 225;
RangeItemPanelMinWidth = 260;
ItemPanelMinHeight = 240;
DayTitleHeight = new GridLength(40, GridUnitType.Pixel);
HeaderMargin = new Thickness(0, 0, 0, _globalToken.MarginSM);
CellLineHeight = CellHeight - 2; // 不知道为啥设置成一样,或者不设置文字有些靠下
RangeCalendarSpacing = 20;
PickerInputMinWidth = 150;
}
}

View File

@ -0,0 +1,10 @@
using AtomUI.Theme;
using AtomUI.Utils;
namespace AtomUI.Controls.DatePickerLang;
[LanguageProvider(LanguageCode.en_US, DatePickerToken.ID)]
internal class en_US : AbstractLanguageProvider
{
public const string Today = "Today";
}

View File

@ -0,0 +1,10 @@
using AtomUI.Theme;
using AtomUI.Utils;
namespace AtomUI.Controls.DatePickerLang;
[LanguageProvider(LanguageCode.zh_CN, DatePickerToken.ID)]
internal class zh_CN : AbstractLanguageProvider
{
public const string Today = "今天";
}

View File

@ -29,8 +29,14 @@
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ComboBoxItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ComboBoxSpinnerInnerBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ComboBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.RangeCalendarItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.RangeCalendarTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CalendarPresenter.CalendarButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CalendarPresenter.CalendarDayButtonTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CalendarPresenter.CalendarItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CalendarPresenter.CalendarTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CalendarPresenter.RangeCalendarItemTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.CalendarPresenter.RangeCalendarTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.DatePickerPresenterTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.DatePickerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.EmptyIndicatorTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ExpanderTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.MenuFlyoutPresenterTheme());

View File

@ -4,6 +4,8 @@
{
internal static void Register()
{
ThemeManager.Current.RegisterLanguageProvider(new AtomUI.Controls.DatePickerLang.en_US());
ThemeManager.Current.RegisterLanguageProvider(new AtomUI.Controls.DatePickerLang.zh_CN());
ThemeManager.Current.RegisterLanguageProvider(new AtomUI.Controls.Localization.en_US());
ThemeManager.Current.RegisterLanguageProvider(new AtomUI.Controls.Localization.zh_CN());
ThemeManager.Current.RegisterLanguageProvider(new AtomUI.Controls.PopupConfirmLang.en_US());

View File

@ -1,5 +1,13 @@
using AtomUI.Theme;
namespace AtomUI.Controls.DatePickerLang
{
public static class DatePickerLangResourceKey
{
public static readonly LanguageResourceKey Today = new LanguageResourceKey("DatePicker.Today", "AtomUI.Lang");
}
}
namespace AtomUI.Controls.Localization
{
public static class CommonLangResourceKey

View File

@ -200,6 +200,7 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey CellLineHeight = new TokenResourceKey("DatePicker.CellLineHeight", "AtomUI.Token");
public static readonly TokenResourceKey CellMargin = new TokenResourceKey("DatePicker.CellMargin", "AtomUI.Token");
public static readonly TokenResourceKey ItemPanelMinWidth = new TokenResourceKey("DatePicker.ItemPanelMinWidth", "AtomUI.Token");
public static readonly TokenResourceKey RangeItemPanelMinWidth = new TokenResourceKey("DatePicker.RangeItemPanelMinWidth", "AtomUI.Token");
public static readonly TokenResourceKey ItemPanelMinHeight = new TokenResourceKey("DatePicker.ItemPanelMinHeight", "AtomUI.Token");
public static readonly TokenResourceKey TextHeight = new TokenResourceKey("DatePicker.TextHeight", "AtomUI.Token");
public static readonly TokenResourceKey PanelContentPadding = new TokenResourceKey("DatePicker.PanelContentPadding", "AtomUI.Token");
@ -207,6 +208,7 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey DayTitleHeight = new TokenResourceKey("DatePicker.DayTitleHeight", "AtomUI.Token");
public static readonly TokenResourceKey HeaderMargin = new TokenResourceKey("DatePicker.HeaderMargin", "AtomUI.Token");
public static readonly TokenResourceKey RangeCalendarSpacing = new TokenResourceKey("DatePicker.RangeCalendarSpacing", "AtomUI.Token");
public static readonly TokenResourceKey PickerInputMinWidth = new TokenResourceKey("DatePicker.PickerInputMinWidth", "AtomUI.Token");
}
public static class EmptyIndicatorTokenResourceKey

View File

@ -47,13 +47,13 @@ internal class InfoPickerInputTheme : BaseControlTheme
};
pickerInnerBox.RegisterInNameScope(scope);
CreateTemplateParentBinding(pickerInnerBox, AddOnDecoratedInnerBox.LeftAddOnContentProperty,
RangeTimePicker.InnerLeftContentProperty);
InfoPickerInput.InnerLeftContentProperty);
CreateTemplateParentBinding(pickerInnerBox, AddOnDecoratedInnerBox.StyleVariantProperty,
RangeTimePicker.StyleVariantProperty);
InfoPickerInput.StyleVariantProperty);
CreateTemplateParentBinding(pickerInnerBox, AddOnDecoratedInnerBox.StatusProperty,
RangeTimePicker.StatusProperty);
InfoPickerInput.StatusProperty);
CreateTemplateParentBinding(pickerInnerBox, AddOnDecoratedInnerBox.SizeTypeProperty,
RangeTimePicker.SizeTypeProperty);
InfoPickerInput.SizeTypeProperty);
var clearUpButton = new PickerClearUpButton()
{
@ -89,14 +89,16 @@ internal class InfoPickerInputTheme : BaseControlTheme
Name = DecoratedBoxPart,
Focusable = true
};
CreateTemplateParentBinding(decoratedBox, AddOnDecoratedBox.MinWidthProperty,
InfoPickerInput.MinWidthProperty);
CreateTemplateParentBinding(decoratedBox, AddOnDecoratedBox.StyleVariantProperty,
RangeTimePicker.StyleVariantProperty);
CreateTemplateParentBinding(decoratedBox, AddOnDecoratedBox.SizeTypeProperty, RangeTimePicker.SizeTypeProperty);
CreateTemplateParentBinding(decoratedBox, AddOnDecoratedBox.StatusProperty, RangeTimePicker.StatusProperty);
InfoPickerInput.StyleVariantProperty);
CreateTemplateParentBinding(decoratedBox, AddOnDecoratedBox.SizeTypeProperty, InfoPickerInput.SizeTypeProperty);
CreateTemplateParentBinding(decoratedBox, AddOnDecoratedBox.StatusProperty, InfoPickerInput.StatusProperty);
CreateTemplateParentBinding(decoratedBox, AddOnDecoratedBox.LeftAddOnProperty,
RangeTimePicker.LeftAddOnProperty);
InfoPickerInput.LeftAddOnProperty);
CreateTemplateParentBinding(decoratedBox, AddOnDecoratedBox.RightAddOnProperty,
RangeTimePicker.RightAddOnProperty);
InfoPickerInput.RightAddOnProperty);
decoratedBox.RegisterInNameScope(scope);
return decoratedBox;
}

View File

@ -51,12 +51,12 @@ internal class RangeInfoPickerInputTheme : InfoPickerInputTheme
var rangeStartTextBox = BuildPickerTextBox(InfoInputBoxPart);
CreateTemplateParentBinding(rangeStartTextBox, TextBox.TextProperty, RangeInfoPickerInput.TextProperty);
CreateTemplateParentBinding(rangeStartTextBox, TextBox.WatermarkProperty,
RangeTimePicker.WatermarkProperty);
RangeInfoPickerInput.WatermarkProperty);
var rangeEndTextBox = BuildPickerTextBox(SecondaryInfoInputBoxPart);
CreateTemplateParentBinding(rangeEndTextBox, TextBox.TextProperty, RangeInfoPickerInput.SecondaryTextProperty);
CreateTemplateParentBinding(rangeEndTextBox, TextBox.WatermarkProperty,
RangeTimePicker.SecondaryWatermarkProperty);
RangeInfoPickerInput.SecondaryWatermarkProperty);
rangeStartTextBox.RegisterInNameScope(scope);
rangeEndTextBox.RegisterInNameScope(scope);
@ -108,7 +108,7 @@ internal class RangeInfoPickerInputTheme : InfoPickerInputTheme
{
{
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(RangePickerIndicatorPart));
indicatorStyle.Add(Layoutable.HeightProperty, TimePickerTokenResourceKey.RangePickerIndicatorThickness);
indicatorStyle.Add(Layoutable.HeightProperty, InfoPickerInputTokenResourceKey.RangePickerIndicatorThickness);
commonStyle.Add(indicatorStyle);
}