diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index f47e4a1713..3316c06bf5 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -252,6 +252,10 @@ namespace Avalonia.Controls
/// drop-down that contains possible matches based on the input in the text
/// box.
///
+ [TemplatePart(ElementPopup, typeof(Popup))]
+ [TemplatePart(ElementSelector, typeof(SelectingItemsControl))]
+ [TemplatePart(ElementSelectionAdapter, typeof(ISelectionAdapter))]
+ [TemplatePart(ElementTextBox, typeof(TextBox))]
[PseudoClasses(":dropdownopen")]
public class AutoCompleteBox : TemplatedControl
{
diff --git a/src/Avalonia.Controls/ButtonSpinner.cs b/src/Avalonia.Controls/ButtonSpinner.cs
index 31aba024ae..29a954098f 100644
--- a/src/Avalonia.Controls/ButtonSpinner.cs
+++ b/src/Avalonia.Controls/ButtonSpinner.cs
@@ -16,6 +16,8 @@ namespace Avalonia.Controls
///
/// Represents a spinner control that includes two Buttons.
///
+ [TemplatePart("PART_DecreaseButton", typeof(Button))]
+ [TemplatePart("PART_IncreaseButton", typeof(Button))]
[PseudoClasses(":left", ":right")]
public class ButtonSpinner : Spinner
{
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 6c83308b39..2dbb5f02f9 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
@@ -222,6 +223,8 @@ namespace Avalonia.Controls
/// element in XAML.
///
///
+ [TemplatePart(PART_ElementMonth, typeof(CalendarItem))]
+ [TemplatePart(PART_ElementRoot, typeof(Panel))]
public class Calendar : TemplatedControl
{
internal const int RowsPerMonth = 7;
@@ -261,6 +264,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register(
nameof(FirstDayOfWeek),
defaultValue: DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek);
+
///
/// Gets or sets the day that is considered the beginning of the week.
///
@@ -273,6 +277,7 @@ namespace Avalonia.Controls
get { return GetValue(FirstDayOfWeekProperty); }
set { SetValue(FirstDayOfWeekProperty, value); }
}
+
///
/// FirstDayOfWeekProperty property changed handler.
///
@@ -289,6 +294,7 @@ namespace Avalonia.Controls
throw new ArgumentOutOfRangeException("d", "Invalid DayOfWeek");
}
}
+
///
/// Inherited code: Requires comment.
///
@@ -311,6 +317,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register(
nameof(IsTodayHighlighted),
defaultValue: true);
+
///
/// Gets or sets a value indicating whether the current date is
/// highlighted.
@@ -324,6 +331,7 @@ namespace Avalonia.Controls
get { return GetValue(IsTodayHighlightedProperty); }
set { SetValue(IsTodayHighlightedProperty, value); }
}
+
///
/// IsTodayHighlightedProperty property changed handler.
///
@@ -343,6 +351,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty HeaderBackgroundProperty =
AvaloniaProperty.Register(nameof(HeaderBackground));
+
public IBrush HeaderBackground
{
get { return GetValue(HeaderBackgroundProperty); }
@@ -367,6 +376,7 @@ namespace Avalonia.Controls
get { return GetValue(DisplayModeProperty); }
set { SetValue(DisplayModeProperty, value); }
}
+
///
/// DisplayModeProperty property changed handler.
///
@@ -424,6 +434,7 @@ namespace Avalonia.Controls
|| mode == CalendarMode.Year
|| mode == CalendarMode.Decade;
}
+
private void OnDisplayModeChanged(CalendarModeChangedEventArgs args)
{
DisplayModeChanged?.Invoke(this, args);
@@ -433,6 +444,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register(
nameof(SelectionMode),
defaultValue: CalendarSelectionMode.SingleDate);
+
///
/// Gets or sets a value that indicates what kind of selections are
/// allowed.
@@ -457,6 +469,7 @@ namespace Avalonia.Controls
get { return GetValue(SelectionModeProperty); }
set { SetValue(SelectionModeProperty, value); }
}
+
private void OnSelectionModeChanged(AvaloniaPropertyChangedEventArgs e)
{
if (IsValidSelectionMode(e.NewValue!))
@@ -471,6 +484,7 @@ namespace Avalonia.Controls
throw new ArgumentOutOfRangeException("d", "Invalid SelectionMode");
}
}
+
///
/// Inherited code: Requires comment.
///
@@ -492,6 +506,7 @@ namespace Avalonia.Controls
o => o.SelectedDate,
(o, v) => o.SelectedDate = v,
defaultBindingMode: BindingMode.TwoWay);
+
///
/// Gets or sets the currently selected date.
///
@@ -720,6 +735,7 @@ namespace Avalonia.Controls
o => o.DisplayDate,
(o, v) => o.DisplayDate = v,
defaultBindingMode: BindingMode.TwoWay);
+
///
/// Gets or sets the date to display.
///
@@ -1973,6 +1989,7 @@ namespace Avalonia.Controls
}
}
}
+
private void Calendar_KeyUp(KeyEventArgs e)
{
if (!e.Handled && (e.Key == Key.LeftShift || e.Key == Key.RightShift))
@@ -1980,6 +1997,7 @@ namespace Avalonia.Controls
ProcessShiftKeyUp();
}
}
+
internal void ProcessShiftKeyUp()
{
if (_isShiftPressed && (SelectionMode == CalendarSelectionMode.SingleRange || SelectionMode == CalendarSelectionMode.MultipleRange))
@@ -2028,6 +2046,7 @@ namespace Avalonia.Controls
}
}
}
+
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
@@ -2054,6 +2073,7 @@ namespace Avalonia.Controls
}
}
}
+
///
/// Called when the IsEnabled property changes.
///
@@ -2098,6 +2118,7 @@ namespace Avalonia.Controls
private const string PART_ElementRoot = "Root";
private const string PART_ElementMonth = "CalendarItem";
+
///
/// Builds the visual tree for the
/// when a new
diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
index f1c56a7331..0ac2056ed1 100644
--- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
@@ -7,6 +7,7 @@ using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
@@ -116,6 +117,10 @@ namespace Avalonia.Controls
Custom = 2
}
+ [TemplatePart(ElementButton, typeof(Button))]
+ [TemplatePart(ElementCalendar, typeof(Calendar))]
+ [TemplatePart(ElementPopup, typeof(Popup))]
+ [TemplatePart(ElementTextBox, typeof(TextBox))]
public class CalendarDatePicker : TemplatedControl
{
private const string ElementTextBox = "PART_TextBox";
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index 616b9083ff..c44994f92f 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -19,6 +19,11 @@ namespace Avalonia.Controls.Primitives
/// Represents the currently displayed month or year on a
/// .
///
+ [TemplatePart(PART_ElementHeaderButton, typeof(Button))]
+ [TemplatePart(PART_ElementMonthView, typeof(Grid))]
+ [TemplatePart(PART_ElementNextButton, typeof(Button))]
+ [TemplatePart(PART_ElementPreviousButton, typeof(Button))]
+ [TemplatePart(PART_ElementYearView, typeof(Grid))]
[PseudoClasses(":calendardisabled")]
public sealed class CalendarItem : TemplatedControl
{
diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs
index 1cad1a4c69..d5923a8b37 100644
--- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs
+++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs
@@ -8,6 +8,10 @@ namespace Avalonia.Controls.Chrome
///
/// Draws window minimize / maximize / close buttons in a when managed client decorations are enabled.
///
+ [TemplatePart("PART_CloseButton", typeof(Panel))]
+ [TemplatePart("PART_RestoreButton", typeof(Panel))]
+ [TemplatePart("PART_MinimiseButton", typeof(Panel))]
+ [TemplatePart("PART_FullScreenButton", typeof(Panel))]
[PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")]
public class CaptionButtons : TemplatedControl
{
diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs
index 4da50e7220..b152a31587 100644
--- a/src/Avalonia.Controls/Chrome/TitleBar.cs
+++ b/src/Avalonia.Controls/Chrome/TitleBar.cs
@@ -8,6 +8,7 @@ namespace Avalonia.Controls.Chrome
///
/// Draws a titlebar when managed client decorations are enabled.
///
+ [TemplatePart("PART_CaptionButtons", typeof(CaptionButtons))]
[PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")]
public class TitleBar : TemplatedControl
{
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index c5410ae9b0..d4e7fc0e47 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -13,12 +13,14 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.VisualTree;
+using Avalonia.Controls.Metadata;
namespace Avalonia.Controls
{
///
/// A drop-down list control.
///
+ [TemplatePart("PART_Popup", typeof(Popup))]
public class ComboBox : SelectingItemsControl
{
///
diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs
index ac7d24be92..b8a45e102f 100644
--- a/src/Avalonia.Controls/ContentControl.cs
+++ b/src/Avalonia.Controls/ContentControl.cs
@@ -1,4 +1,5 @@
using Avalonia.Collections;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
@@ -12,6 +13,7 @@ namespace Avalonia.Controls
///
/// Displays according to a .
///
+ [TemplatePart("PART_ContentPresenter", typeof(IContentPresenter))]
public class ContentControl : TemplatedControl, IContentControl, IContentPresenterHost
{
///
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
index 6c74eb6b91..f2b808fe0d 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -14,6 +14,15 @@ namespace Avalonia.Controls
///
/// A control to allow the user to select a date
///
+ [TemplatePart("ButtonContentGrid", typeof(Grid))]
+ [TemplatePart("DayText", typeof(TextBlock))]
+ [TemplatePart("FirstSpacer", typeof(Rectangle))]
+ [TemplatePart("FlyoutButton", typeof(Button))]
+ [TemplatePart("MonthText", typeof(TextBlock))]
+ [TemplatePart("PickerPresenter", typeof(DatePickerPresenter))]
+ [TemplatePart("Popup", typeof(Popup))]
+ [TemplatePart("SecondSpacer", typeof(Rectangle))]
+ [TemplatePart("YearText", typeof(TextBlock))]
[PseudoClasses(":hasnodate")]
public class DatePicker : TemplatedControl
{
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
index 0caba64758..0612efe14d 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
@@ -1,4 +1,5 @@
-using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -12,6 +13,23 @@ namespace Avalonia.Controls
/// Defines the presenter used for selecting a date for a
///
///
+ [TemplatePart("AcceptButton", typeof(Button))]
+ [TemplatePart("DayDownButton", typeof(RepeatButton))]
+ [TemplatePart("DayHost", typeof(Panel))]
+ [TemplatePart("DaySelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("DayUpButton", typeof(RepeatButton))]
+ [TemplatePart("DismissButton", typeof(Button))]
+ [TemplatePart("FirstSpacer", typeof(Rectangle))]
+ [TemplatePart("MonthDownButton", typeof(RepeatButton))]
+ [TemplatePart("MonthHost", typeof(Panel))]
+ [TemplatePart("MonthSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("MonthUpButton", typeof(RepeatButton))]
+ [TemplatePart("PickerContainer", typeof(Grid))]
+ [TemplatePart("SecondSpacer", typeof(Rectangle))]
+ [TemplatePart("YearDownButton", typeof(RepeatButton))]
+ [TemplatePart("YearHost", typeof(Panel))]
+ [TemplatePart("YearSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("YearUpButton", typeof(RepeatButton))]
public class DatePickerPresenter : PickerPresenterBase
{
///
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
index 3aab49d556..f04c79505e 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
@@ -12,6 +12,18 @@ namespace Avalonia.Controls
///
/// A control to allow the user to select a time.
///
+ [TemplatePart("FirstColumnDivider", typeof(Rectangle))]
+ [TemplatePart("FirstPickerHost", typeof(Border))]
+ [TemplatePart("FlyoutButton", typeof(Button))]
+ [TemplatePart("FlyoutButtonContentGrid", typeof(Grid))]
+ [TemplatePart("HourTextBlock", typeof(TextBlock))]
+ [TemplatePart("MinuteTextBlock", typeof(TextBlock))]
+ [TemplatePart("PeriodTextBlock", typeof(TextBlock))]
+ [TemplatePart("PickerPresenter", typeof(TimePickerPresenter))]
+ [TemplatePart("Popup", typeof(Popup))]
+ [TemplatePart("SecondColumnDivider", typeof(Rectangle))]
+ [TemplatePart("SecondPickerHost", typeof(Border))]
+ [TemplatePart("ThirdPickerHost", typeof(Border))]
[PseudoClasses(":hasnotime")]
public class TimePicker : TemplatedControl
{
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
index 30b6144388..7f2abb7e98 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
@@ -1,4 +1,5 @@
-using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -10,6 +11,20 @@ namespace Avalonia.Controls
/// Defines the presenter used for selecting a time. Intended for use with
/// but can be used independently
///
+ [TemplatePart("AcceptButton", typeof(Button))]
+ [TemplatePart("DismissButton", typeof(Button))]
+ [TemplatePart("HourDownButton", typeof(RepeatButton))]
+ [TemplatePart("HourSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("HourUpButton", typeof(RepeatButton))]
+ [TemplatePart("MinuteDownButton", typeof(RepeatButton))]
+ [TemplatePart("MinuteSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("MinuteUpButton", typeof(RepeatButton))]
+ [TemplatePart("PeriodDownButton", typeof(RepeatButton))]
+ [TemplatePart("PeriodHost", typeof(Panel))]
+ [TemplatePart("PeriodSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PeriodUpButton", typeof(RepeatButton))]
+ [TemplatePart("PickerContainer", typeof(Grid))]
+ [TemplatePart("SecondSpacer", typeof(Rectangle))]
public class TimePickerPresenter : PickerPresenterBase
{
///
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index 9b7ae0d324..79285bb86b 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -1,5 +1,6 @@
using System.Collections;
using Avalonia.Controls.Generators;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection;
@@ -12,6 +13,7 @@ namespace Avalonia.Controls
///
/// An in which individual items can be selected.
///
+ [TemplatePart("PART_ScrollViewer", typeof(IScrollable))]
public class ListBox : SelectingItemsControl
{
///
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index 34215e9713..cddd621b8e 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -20,6 +20,7 @@ namespace Avalonia.Controls
///
/// A menu item control.
///
+ [TemplatePart("PART_Popup", typeof(Popup))]
[PseudoClasses(":separator", ":icon", ":open", ":pressed", ":selected")]
public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable, ICommandSource
{
diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
index 9499995da3..d6b82a8f8a 100644
--- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
+++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
@@ -14,6 +14,7 @@ namespace Avalonia.Controls.Notifications
///
/// An that displays notifications in a .
///
+ [TemplatePart("PART_Items", typeof(Panel))]
[PseudoClasses(":topleft", ":topright", ":bottomleft", ":bottomright")]
public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
{
diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
index 91f21f848f..fbbaab6182 100644
--- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
+++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
@@ -2,6 +2,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
@@ -15,6 +16,8 @@ namespace Avalonia.Controls
///
/// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
///
+ [TemplatePart("PART_Spinner", typeof(Spinner))]
+ [TemplatePart("PART_TextBox", typeof(TextBox))]
public class NumericUpDown : TemplatedControl
{
///
diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs
index 8460fe3017..6a30097fbb 100644
--- a/src/Avalonia.Controls/Primitives/ScrollBar.cs
+++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs
@@ -22,6 +22,10 @@ namespace Avalonia.Controls.Primitives
///
/// A scrollbar control.
///
+ [TemplatePart("PART_LineDownButton", typeof(Button))]
+ [TemplatePart("PART_LineUpButton", typeof(Button))]
+ [TemplatePart("PART_PageDownButton", typeof(Button))]
+ [TemplatePart("PART_PageUpButton", typeof(Button))]
[PseudoClasses(":vertical", ":horizontal")]
public class ScrollBar : RangeBase
{
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index 1d8d6aef45..a4f2cc799a 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -10,6 +10,7 @@ namespace Avalonia.Controls
///
/// A control used to indicate the progress of an operation.
///
+ [TemplatePart("PART_Indicator", typeof(Border))]
[PseudoClasses(":vertical", ":horizontal", ":indeterminate")]
public class ProgressBar : RangeBase
{
diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs
index 73bb827ad9..535f9ae43e 100644
--- a/src/Avalonia.Controls/ScrollViewer.cs
+++ b/src/Avalonia.Controls/ScrollViewer.cs
@@ -1,6 +1,7 @@
using System;
using System.Reactive.Linq;
using Avalonia.Automation.Peers;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
@@ -11,6 +12,8 @@ namespace Avalonia.Controls
///
/// A control which scrolls its content if the content is bigger than the space available.
///
+ [TemplatePart("PART_HorizontalScrollBar", typeof(ScrollBar))]
+ [TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))]
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider
{
///
diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs
index 574638c2bc..f2bd1947d6 100644
--- a/src/Avalonia.Controls/Slider.cs
+++ b/src/Avalonia.Controls/Slider.cs
@@ -42,6 +42,9 @@ namespace Avalonia.Controls
///
/// A control that lets the user select from a range of values by moving a Thumb control along a Track.
///
+ [TemplatePart("PART_DecreaseButton", typeof(Button))]
+ [TemplatePart("PART_IncreaseButton", typeof(Button))]
+ [TemplatePart("PART_Track", typeof(Track))]
[PseudoClasses(":vertical", ":horizontal", ":pressed")]
public class Slider : RangeBase
{
diff --git a/src/Avalonia.Controls/SplitButton/SplitButton.cs b/src/Avalonia.Controls/SplitButton/SplitButton.cs
index f1d07b2679..69922f279c 100644
--- a/src/Avalonia.Controls/SplitButton/SplitButton.cs
+++ b/src/Avalonia.Controls/SplitButton/SplitButton.cs
@@ -13,6 +13,8 @@ namespace Avalonia.Controls
/// A button with primary and secondary parts that can each be pressed separately.
/// The primary part behaves like a and the secondary part opens a flyout.
///
+ [TemplatePart("PART_PrimaryButton", typeof(Button))]
+ [TemplatePart("PART_SecondaryButton", typeof(Button))]
[PseudoClasses(pcFlyoutOpen, pcPressed)]
public class SplitButton : ContentControl, ICommandSource
{
diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView.cs
index d2161deb6e..ae1605a985 100644
--- a/src/Avalonia.Controls/SplitView.cs
+++ b/src/Avalonia.Controls/SplitView.cs
@@ -77,6 +77,7 @@ namespace Avalonia.Controls
///
/// A control with two views: A collapsible pane and an area for content
///
+ [TemplatePart("PART_PaneRoot", typeof(Panel))]
[PseudoClasses(":open", ":closed")]
[PseudoClasses(":compactoverlay", ":compactinline", ":overlay", ":inline")]
[PseudoClasses(":left", ":right")]
diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs
index 1fbe9cc4db..70fecc7ce1 100644
--- a/src/Avalonia.Controls/TabControl.cs
+++ b/src/Avalonia.Controls/TabControl.cs
@@ -11,12 +11,14 @@ using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Avalonia.Automation;
+using Avalonia.Controls.Metadata;
namespace Avalonia.Controls
{
///
/// A tab control that displays a tab strip along with the content of the selected tab.
///
+ [TemplatePart("PART_ItemsPresenter", typeof(ItemsPresenter))]
public class TabControl : SelectingItemsControl, IContentPresenterHost
{
///
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 0641901ed5..f8238d1da2 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -23,6 +23,7 @@ namespace Avalonia.Controls
///
/// Represents a control that can be used to display or edit unformatted text.
///
+ [TemplatePart("PART_TextPresenter", typeof(TextPresenter))]
[PseudoClasses(":empty")]
public class TextBox : TemplatedControl, UndoRedoHelper.IUndoRedoHost
{
diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs
index f33f2b9df3..fd6c202c6f 100644
--- a/src/Avalonia.Controls/ToggleSwitch.cs
+++ b/src/Avalonia.Controls/ToggleSwitch.cs
@@ -9,6 +9,8 @@ namespace Avalonia.Controls
///
/// A Toggle Switch control.
///
+ [TemplatePart("MovingKnobs", typeof(Panel))]
+ [TemplatePart("SwitchKnob", typeof(Panel))]
[PseudoClasses(":dragging")]
public class ToggleSwitch : ToggleButton
{
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index a4fe154515..95a597e831 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -1,5 +1,6 @@
using System;
using System.Reactive.Linq;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
@@ -26,6 +27,7 @@ namespace Avalonia.Controls
/// It handles scheduling layout, styling and rendering as well as
/// tracking the widget's .
///
+ [TemplatePart("PART_TransparencyFallback", typeof(Border))]
public abstract class TopLevel : ContentControl,
IInputRoot,
ILayoutRoot,
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 20c0ed386d..a0a3c09942 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -12,6 +12,7 @@ namespace Avalonia.Controls
///
/// An item in a .
///
+ [TemplatePart("PART_Header", typeof(IControl))]
[PseudoClasses(":pressed", ":selected")]
public class TreeViewItem : HeaderedItemsControl, ISelectable
{
diff --git a/src/Avalonia.Dialogs/ManagedFileChooser.cs b/src/Avalonia.Dialogs/ManagedFileChooser.cs
index 9058c405a3..199a4d6620 100644
--- a/src/Avalonia.Dialogs/ManagedFileChooser.cs
+++ b/src/Avalonia.Dialogs/ManagedFileChooser.cs
@@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -9,6 +10,8 @@ using Avalonia.LogicalTree;
namespace Avalonia.Dialogs
{
+ [TemplatePart("QuickLinks", typeof(Control))]
+ [TemplatePart("Files", typeof(ListBox))]
public class ManagedFileChooser : TemplatedControl
{
private Control _quickLinksRoot;
diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs
index e974bbb100..aa730b3219 100644
--- a/src/Avalonia.Visuals/Media/Color.cs
+++ b/src/Avalonia.Visuals/Media/Color.cs
@@ -258,7 +258,7 @@ namespace Avalonia.Media
public override string ToString()
{
uint rgb = ToUint32();
- return KnownColors.GetKnownColorName(rgb) ?? $"#{rgb:x8}";
+ return KnownColors.GetKnownColorName(rgb) ?? $"#{rgb.ToString("x8", CultureInfo.InvariantCulture)}";
}
///
diff --git a/src/Avalonia.Visuals/Media/HsvColor.cs b/src/Avalonia.Visuals/Media/HsvColor.cs
index 8b2f10d088..4a0277b4d4 100644
--- a/src/Avalonia.Visuals/Media/HsvColor.cs
+++ b/src/Avalonia.Visuals/Media/HsvColor.cs
@@ -4,6 +4,8 @@
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
+using System.Globalization;
+using System.Text;
using Avalonia.Utilities;
namespace Avalonia.Media
@@ -20,7 +22,8 @@ namespace Avalonia.Media
/// Initializes a new instance of the struct.
///
/// The Alpha (transparency) channel value in the range from 0..1.
- /// The Hue channel value in the range from 0..360.
+ /// The Hue channel value in the range from 0..360.
+ /// Note that 360 is equivalent to 0 and will be adjusted automatically.
/// The Saturation channel value in the range from 0..1.
/// The Value channel value in the range from 0..1.
public HsvColor(
@@ -33,6 +36,12 @@ namespace Avalonia.Media
H = MathUtilities.Clamp(hue, 0.0, 360.0);
S = MathUtilities.Clamp(saturation, 0.0, 1.0);
V = MathUtilities.Clamp(value, 0.0, 1.0);
+
+ // The maximum value of Hue is technically 360 minus epsilon (just below 360).
+ // This is because, in a color circle, 360 degrees is equivalent to 0 degrees.
+ // However, that is too tricky to work with in code and isn't as intuitive.
+ // Therefore, since 360 == 0, just wrap 360 if needed back to 0.
+ H = (H == 360.0 ? 0 : H);
}
///
@@ -43,7 +52,8 @@ namespace Avalonia.Media
/// Whether or not the channel values are in the correct ranges must be known.
///
/// The Alpha (transparency) channel value in the range from 0..1.
- /// The Hue channel value in the range from 0..360.
+ /// The Hue channel value in the range from 0..360.
+ /// Note that 360 is equivalent to 0 and will be adjusted automatically.
/// The Saturation channel value in the range from 0..1.
/// The Value channel value in the range from 0..1.
/// Whether to clamp channel values to their required ranges.
@@ -60,6 +70,9 @@ namespace Avalonia.Media
H = MathUtilities.Clamp(hue, 0.0, 360.0);
S = MathUtilities.Clamp(saturation, 0.0, 1.0);
V = MathUtilities.Clamp(value, 0.0, 1.0);
+
+ // See comments in constructor above
+ H = (H == 360.0 ? 0 : H);
}
else
{
@@ -91,6 +104,7 @@ namespace Avalonia.Media
///
/// Gets the Hue channel value in the range from 0..360.
+ /// Note that 360 is equivalent to 0 and will be adjusted automatically.
///
public double H { get; }
@@ -155,6 +169,129 @@ namespace Avalonia.Media
return HsvColor.ToRgb(H, S, V, A);
}
+ ///
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+
+ // Use a format similar to HSL in HTML/CSS "hsla(0, 100%, 50%, 0.5)"
+ //
+ // However:
+ // - To ensure precision is never lost, allow decimal places
+ // - To maintain numerical consistency do not use percent
+ //
+ // Example:
+ //
+ // hsva(hue, saturation, value, alpha)
+ // hsva(230, 1.0, 0.5, 1.0)
+ //
+ // Where:
+ //
+ // hue : double from 0 to 360
+ // saturation : double from 0 to 1
+ // (HTML uses a percentage)
+ // value : double from 0 to 1
+ // (HTML uses a percentage)
+ // alpha : double from 0 to 1
+ // (HTML does not use a percentage for alpha)
+
+ sb.Append("hsva(");
+ sb.Append(H.ToString(CultureInfo.InvariantCulture));
+ sb.Append(", ");
+ sb.Append(S.ToString(CultureInfo.InvariantCulture));
+ sb.Append(", ");
+ sb.Append(V.ToString(CultureInfo.InvariantCulture));
+ sb.Append(", ");
+ sb.Append(A.ToString(CultureInfo.InvariantCulture));
+ sb.Append(')');
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Parses an HSV color string.
+ ///
+ /// The HSV color string to parse.
+ /// The parsed .
+ public static HsvColor Parse(string s)
+ {
+ if (s is null)
+ {
+ throw new ArgumentNullException(nameof(s));
+ }
+
+ if (TryParse(s, out HsvColor hsvColor))
+ {
+ return hsvColor;
+ }
+
+ throw new FormatException($"Invalid HSV color string: '{s}'.");
+ }
+
+ ///
+ /// Parses an HSV color string.
+ ///
+ /// The HSV color string to parse.
+ /// The parsed .
+ /// True if parsing was successful; otherwise, false.
+ public static bool TryParse(string s, out HsvColor hsvColor)
+ {
+ hsvColor = default;
+
+ if (s is null)
+ {
+ return false;
+ }
+
+ string workingString = s.Trim();
+
+ if (workingString.Length == 0 ||
+ workingString.IndexOf(",", StringComparison.Ordinal) < 0)
+ {
+ return false;
+ }
+
+ if (workingString.Length > 6 &&
+ workingString.StartsWith("hsva(", StringComparison.OrdinalIgnoreCase) &&
+ workingString.EndsWith(")", StringComparison.Ordinal))
+ {
+ workingString = workingString.Substring(5, workingString.Length - 6);
+ }
+
+ if (workingString.Length > 5 &&
+ workingString.StartsWith("hsv(", StringComparison.OrdinalIgnoreCase) &&
+ workingString.EndsWith(")", StringComparison.Ordinal))
+ {
+ workingString = workingString.Substring(4, workingString.Length - 5);
+ }
+
+ string[] components = workingString.Split(',');
+
+ if (components.Length == 3) // HSV
+ {
+ if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
+ double.TryParse(components[1], NumberStyles.Number, CultureInfo.InvariantCulture, out double saturation) &&
+ double.TryParse(components[2], NumberStyles.Number, CultureInfo.InvariantCulture, out double value))
+ {
+ hsvColor = new HsvColor(1.0, hue, saturation, value);
+ return true;
+ }
+ }
+ else if (components.Length == 4) // HSVA
+ {
+ if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) &&
+ double.TryParse(components[1], NumberStyles.Number, CultureInfo.InvariantCulture, out double saturation) &&
+ double.TryParse(components[2], NumberStyles.Number, CultureInfo.InvariantCulture, out double value) &&
+ double.TryParse(components[3], NumberStyles.Number, CultureInfo.InvariantCulture, out double alpha))
+ {
+ hsvColor = new HsvColor(alpha, hue, saturation, value);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
///
/// Creates a new from individual color channel values.
///
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
index e218da5f41..64fafc65f3 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
@@ -1,7 +1,9 @@
using System;
+using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods;
using static Avalonia.LinuxFramebuffer.Output.LibDrm;
@@ -142,10 +144,28 @@ namespace Avalonia.LinuxFramebuffer.Output
public int Fd { get; private set; }
public DrmCard(string path = null)
{
- path = path ?? "/dev/dri/card0";
- Fd = open(path, 2, 0);
- if (Fd == -1)
- throw new Win32Exception("Couldn't open " + path);
+ if(path == null)
+ {
+ var files = Directory.GetFiles("/dev/dri/");
+
+ foreach(var file in files)
+ {
+ var match = Regex.Match(file, "card[0-9]+");
+
+ if(match.Success)
+ {
+ Fd = open(file, 2, 0);
+ if(Fd != -1) break;
+ }
+ }
+
+ if(Fd == -1) throw new Win32Exception("Couldn't open /dev/dri/card[0-9]+");
+ }
+ else
+ {
+ Fd = open(path, 2, 0);
+ if(Fd != -1) throw new Win32Exception($"Couldn't open {path}");
+ }
}
public DrmResources GetResources() => new DrmResources(Fd);