diff --git a/readme.md b/readme.md index 96cfde3eb2..2b26cbdd1a 100644 --- a/readme.md +++ b/readme.md @@ -35,7 +35,7 @@ https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts ## Documentation -As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia). +As mentioned above, Avalonia is still in beta and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia). There's also a high-level [architecture document](http://avaloniaui.net/architecture/project-structure) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/. diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b151cabf43..a2048005a4 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -10,6 +10,7 @@ namespace ControlCatalog { internal class Program { + [STAThread] static void Main(string[] args) { // TODO: Make this work with GTK/Skia/Cairo depending on command-line args diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 346535d39d..b45a93455e 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -9,8 +9,10 @@ namespace ControlCatalog.NetCore { static class Program { + static void Main(string[] args) { + Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); if (args.Contains("--wait-for-attach")) { Console.WriteLine("Attach debugger and use 'Set next statement'"); diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 862de9d320..b8a8479a49 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -1,201 +1,17 @@  - netstandard2.0 - False - false + netstandard2.0 - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - - - App.xaml - - - MainView.xaml - - - DecoratedWindow.xaml - - - MainWindow.xaml - - - DialogsPage.xaml - - - BorderPage.xaml - - - AutoCompleteBoxPage.xaml - - - ButtonPage.xaml - - - CalendarPage.xaml - - - CanvasPage.xaml - - - CarouselPage.xaml - - - ContextMenuPage.xaml - - - CheckBoxPage.xaml - - - DropDownPage.xaml - - - DatePickerPage.xaml - - - ExpanderPage.xaml - - - ImagePage.xaml + + %(Filename) - - LayoutTransformControlPage.xaml - - - MenuPage.xaml - - - ProgressBarPage.xaml - - - RadioButtonPage.xaml - - - SliderPage.xaml - - - TreeViewPage.xaml - - - TextBoxPage.xaml - - - ToolTipPage.xaml - - - ButtonSpinnerPage.xaml - - - NumericUpDownPage.xaml - - - - - - - - - - - - + Designer + + @@ -212,20 +28,6 @@ - - - - - - Designer - - - - - MSBuild:Compile - - - - + \ No newline at end of file diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index a0e0df450b..1107d34b3e 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -15,6 +15,7 @@ + diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml b/samples/ControlCatalog/Pages/DragAndDropPage.xaml new file mode 100644 index 0000000000..af679d2f9a --- /dev/null +++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml @@ -0,0 +1,19 @@ + + + Drag+Drop + Example of Drag+Drop capabilities + + + + Drag Me + + + Drop some text or files here + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs new file mode 100644 index 0000000000..718f21314e --- /dev/null +++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs @@ -0,0 +1,71 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Markup.Xaml; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ControlCatalog.Pages +{ + public class DragAndDropPage : UserControl + { + private TextBlock _DropState; + private TextBlock _DragState; + private Border _DragMe; + private int DragCount = 0; + + public DragAndDropPage() + { + this.InitializeComponent(); + + _DragMe.PointerPressed += DoDrag; + + AddHandler(DragDrop.DropEvent, Drop); + AddHandler(DragDrop.DragOverEvent, DragOver); + } + + private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e) + { + DataObject dragData = new DataObject(); + dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times"); + + var result = await DragDrop.DoDragDrop(dragData, DragDropEffects.Copy); + switch(result) + { + case DragDropEffects.Copy: + _DragState.Text = "The text was copied"; break; + case DragDropEffects.Link: + _DragState.Text = "The text was linked"; break; + case DragDropEffects.None: + _DragState.Text = "The drag operation was canceled"; break; + } + } + + private void DragOver(object sender, DragEventArgs e) + { + // Only allow Copy or Link as Drop Operations. + e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link); + + // Only allow if the dragged data contains text or filenames. + if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames)) + e.DragEffects = DragDropEffects.None; + } + + private void Drop(object sender, DragEventArgs e) + { + if (e.Data.Contains(DataFormats.Text)) + _DropState.Text = e.Data.GetText(); + else if (e.Data.Contains(DataFormats.FileNames)) + _DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames()); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + + _DropState = this.Find("DropState"); + _DragState = this.Find("DragState"); + _DragMe = this.Find("DragMe"); + } + } +} diff --git a/samples/ControlCatalog/Properties/AssemblyInfo.cs b/samples/ControlCatalog/Properties/AssemblyInfo.cs deleted file mode 100644 index 30c069d7d8..0000000000 --- a/samples/ControlCatalog/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ControlCatalog")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ControlCatalog")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("61bec86c-f307-4295-b5b8-9428610d7d55")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Avalonia.Base/Utilities/StringTokenizer.cs b/src/Avalonia.Base/Utilities/StringTokenizer.cs new file mode 100644 index 0000000000..2559e52932 --- /dev/null +++ b/src/Avalonia.Base/Utilities/StringTokenizer.cs @@ -0,0 +1,205 @@ +using System; +using System.Globalization; +using static System.Char; + +namespace Avalonia.Utilities +{ + public struct StringTokenizer : IDisposable + { + private const char DefaultSeparatorChar = ','; + + private readonly string _s; + private readonly int _length; + private readonly char _separator; + private readonly string _exceptionMessage; + private readonly IFormatProvider _formatProvider; + private int _index; + private int _tokenIndex; + private int _tokenLength; + + public StringTokenizer(string s, IFormatProvider formatProvider, string exceptionMessage = null) + : this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage) + { + _formatProvider = formatProvider; + } + + public StringTokenizer(string s, char separator = DefaultSeparatorChar, string exceptionMessage = null) + { + _s = s ?? throw new ArgumentNullException(nameof(s)); + _length = s?.Length ?? 0; + _separator = separator; + _exceptionMessage = exceptionMessage; + _formatProvider = CultureInfo.InvariantCulture; + _index = 0; + _tokenIndex = -1; + _tokenLength = 0; + + while (_index < _length && IsWhiteSpace(_s, _index)) + { + _index++; + } + } + + public string CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength); + + public void Dispose() + { + if (_index != _length) + { + throw GetFormatException(); + } + } + + public bool TryReadInt32(out Int32 result, char? separator = null) + { + var success = TryReadString(out var stringResult, separator); + result = success ? int.Parse(stringResult, _formatProvider) : 0; + return success; + } + + public int ReadInt32(char? separator = null) + { + if (!TryReadInt32(out var result, separator)) + { + throw GetFormatException(); + } + + return result; + } + + public bool TryReadDouble(out double result, char? separator = null) + { + var success = TryReadString(out var stringResult, separator); + result = success ? double.Parse(stringResult, _formatProvider) : 0; + return success; + } + + public double ReadDouble(char? separator = null) + { + if (!TryReadDouble(out var result, separator)) + { + throw GetFormatException(); + } + + return result; + } + + public bool TryReadString(out string result, char? separator = null) + { + var success = TryReadToken(separator ?? _separator); + result = CurrentToken; + return success; + } + + public string ReadString(char? separator = null) + { + if (!TryReadString(out var result, separator)) + { + throw GetFormatException(); + } + + return result; + } + + private bool TryReadToken(char separator) + { + _tokenIndex = -1; + + if (_index >= _length) + { + return false; + } + + var c = _s[_index]; + + var index = _index; + var length = 0; + + while (_index < _length) + { + c = _s[_index]; + + if (IsWhiteSpace(c) || c == separator) + { + break; + } + + _index++; + length++; + } + + SkipToNextToken(separator); + + _tokenIndex = index; + _tokenLength = length; + + if (_tokenLength < 1) + { + throw GetFormatException(); + } + + return true; + } + + private void SkipToNextToken(char separator) + { + if (_index < _length) + { + var c = _s[_index]; + + if (c != separator && !IsWhiteSpace(c)) + { + throw GetFormatException(); + } + + var length = 0; + + while (_index < _length) + { + c = _s[_index]; + + if (c == separator) + { + length++; + _index++; + + if (length > 1) + { + throw GetFormatException(); + } + } + else + { + if (!IsWhiteSpace(c)) + { + break; + } + + _index++; + } + } + + if (length > 0 && _index >= _length) + { + throw GetFormatException(); + } + } + } + + private FormatException GetFormatException() => + _exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException(); + + private static char GetSeparatorFromFormatProvider(IFormatProvider provider) + { + var c = DefaultSeparatorChar; + + var formatInfo = NumberFormatInfo.GetInstance(provider); + if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0]) + { + c = ';'; + } + + return c; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 06c1a8b4cc..6fdca557eb 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -2,16 +2,17 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive.Concurrency; using System.Threading; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.Input.Raw; using Avalonia.Layout; -using Avalonia.Rendering; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.Threading; -using System.Reactive.Concurrency; namespace Avalonia { @@ -234,7 +235,9 @@ namespace Avalonia .Bind().ToConstant(_styler) .Bind().ToSingleton() .Bind().ToConstant(this) - .Bind().ToConstant(AvaloniaScheduler.Instance); + .Bind().ToConstant(AvaloniaScheduler.Instance) + .Bind().ToConstant(DragDropDevice.Instance) + .Bind().ToTransient(); } } } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index fdb04f4ade..78dc994df7 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -19,7 +19,7 @@ namespace Avalonia.Controls { ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); - MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); + MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); } /// @@ -75,13 +75,14 @@ namespace Avalonia.Controls { if (control != null) { - if(_popup == null) + if (_popup == null) { _popup = new Popup() { PlacementMode = PlacementMode.Pointer, PlacementTarget = control, - StaysOpen = false + StaysOpen = false, + ObeyScreenEdges = true }; _popup.Closed += PopupClosed; diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index c711553e05..789953a249 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Utilities; using System; using System.Collections.Generic; using System.Globalization; @@ -210,7 +211,13 @@ namespace Avalonia.Controls /// The . public static IEnumerable ParseLengths(string s, CultureInfo culture) { - return s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(x => Parse(x, culture)); + using (var tokenizer = new StringTokenizer(s, culture)) + { + while (tokenizer.TryReadString(out var item)) + { + yield return Parse(item, culture); + } + } } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs new file mode 100644 index 0000000000..e136efe2a9 --- /dev/null +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -0,0 +1,210 @@ +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Input.Raw; +using Avalonia.Threading; +using Avalonia.VisualTree; + +namespace Avalonia.Platform +{ + class InProcessDragSource : IPlatformDragSource + { + private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton; + private readonly IDragDropDevice _dragDrop; + private readonly IInputManager _inputManager; + private readonly Subject _result = new Subject(); + + private DragDropEffects _allowedEffects; + private IDataObject _draggedData; + private IInputElement _lastRoot; + private Point _lastPosition; + private StandardCursorType _lastCursorType; + private object _originalCursor; + private InputModifiers? _initialInputModifiers; + + public InProcessDragSource() + { + _inputManager = AvaloniaLocator.Current.GetService(); + _dragDrop = AvaloniaLocator.Current.GetService(); + } + + public async Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects) + { + Dispatcher.UIThread.VerifyAccess(); + if (_draggedData == null) + { + _draggedData = data; + _lastRoot = null; + _lastPosition = default(Point); + _allowedEffects = allowedEffects; + + using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents)) + { + using (_inputManager.PreProcess.OfType().Subscribe(ProcessKeyEvents)) + { + var effect = await _result.FirstAsync(); + return effect; + } + } + } + return DragDropEffects.None; + } + + + private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, InputModifiers modifiers) + { + _lastPosition = pt; + + RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, _allowedEffects); + var tl = root.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + tl.PlatformImpl.Input(rawEvent); + + var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers); + UpdateCursor(root, effect); + return effect; + } + + private DragDropEffects GetPreferredEffect(DragDropEffects effect, InputModifiers modifiers) + { + if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None) + return effect; // No need to check for the modifiers. + if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(InputModifiers.Alt)) + return DragDropEffects.Link; + if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(InputModifiers.Control)) + return DragDropEffects.Copy; + return DragDropEffects.Move; + } + + private StandardCursorType GetCursorForDropEffect(DragDropEffects effects) + { + if (effects.HasFlag(DragDropEffects.Copy)) + return StandardCursorType.DragCopy; + if (effects.HasFlag(DragDropEffects.Move)) + return StandardCursorType.DragMove; + if (effects.HasFlag(DragDropEffects.Link)) + return StandardCursorType.DragLink; + return StandardCursorType.No; + } + + private void UpdateCursor(IInputElement root, DragDropEffects effect) + { + if (_lastRoot != root) + { + if (_lastRoot is InputElement ieLast) + { + if (_originalCursor == AvaloniaProperty.UnsetValue) + ieLast.ClearValue(InputElement.CursorProperty); + else + ieLast.Cursor = _originalCursor as Cursor; + } + + if (root is InputElement ieNew) + { + if (!ieNew.IsSet(InputElement.CursorProperty)) + _originalCursor = AvaloniaProperty.UnsetValue; + else + _originalCursor = root.Cursor; + } + else + _originalCursor = null; + + _lastCursorType = StandardCursorType.Arrow; + _lastRoot = root; + } + + if (root is InputElement ie) + { + var ct = GetCursorForDropEffect(effect); + if (ct != _lastCursorType) + { + _lastCursorType = ct; + ie.Cursor = new Cursor(ct); + } + } + } + + private void CancelDragging() + { + if (_lastRoot != null) + RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, InputModifiers.None); + UpdateCursor(null, DragDropEffects.None); + _result.OnNext(DragDropEffects.None); + } + + private void ProcessKeyEvents(RawKeyEventArgs e) + { + if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape) + { + if (_lastRoot != null) + RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, e.Modifiers); + UpdateCursor(null, DragDropEffects.None); + _result.OnNext(DragDropEffects.None); + e.Handled = true; + } + else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt) + RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers); + } + + private void ProcessMouseEvents(RawMouseEventArgs e) + { + if (!_initialInputModifiers.HasValue) + _initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS; + + + void CheckDraggingAccepted(InputModifiers changedMouseButton) + { + if (_initialInputModifiers.Value.HasFlag(changedMouseButton)) + { + var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers); + UpdateCursor(null, DragDropEffects.None); + _result.OnNext(result); + } + else + CancelDragging(); + e.Handled = true; + } + + switch (e.Type) + { + case RawMouseEventType.LeftButtonDown: + case RawMouseEventType.RightButtonDown: + case RawMouseEventType.MiddleButtonDown: + case RawMouseEventType.NonClientLeftButtonDown: + CancelDragging(); + e.Handled = true; + return; + case RawMouseEventType.LeaveWindow: + RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position, e.InputModifiers); break; + case RawMouseEventType.LeftButtonUp: + CheckDraggingAccepted(InputModifiers.LeftMouseButton); break; + case RawMouseEventType.MiddleButtonUp: + CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break; + case RawMouseEventType.RightButtonUp: + CheckDraggingAccepted(InputModifiers.RightMouseButton); break; + case RawMouseEventType.Move: + var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS; + if (_initialInputModifiers.Value != mods) + { + CancelDragging(); + e.Handled = true; + return; + } + + if (e.Root != _lastRoot) + { + if (_lastRoot != null) + RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), e.InputModifiers); + RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers); + } + else + RaiseEventAndUpdateCursor(RawDragEventType.DragOver, e.Root, e.Position, e.InputModifiers); + break; + } + } + } +} diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 5cd3b22fc9..656f3890cd 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -40,6 +40,12 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); + /// + /// Defines the property. + /// + public static readonly StyledProperty ObeyScreenEdgesProperty = + AvaloniaProperty.Register(nameof(ObeyScreenEdges)); + /// /// Defines the property. /// @@ -136,6 +142,16 @@ namespace Avalonia.Controls.Primitives set { SetValue(PlacementModeProperty, value); } } + /// + /// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary + /// when its opened at a position where it would otherwise overlap the screen edge. + /// + public bool ObeyScreenEdges + { + get => GetValue(ObeyScreenEdgesProperty); + set => SetValue(ObeyScreenEdgesProperty, value); + } + /// /// Gets or sets the Horizontal offset of the popup in relation to the /// @@ -215,7 +231,17 @@ namespace Avalonia.Controls.Primitives { var window = _topLevel as Window; if (window != null) + { window.Deactivated += WindowDeactivated; + } + else + { + var parentPopuproot = _topLevel as PopupRoot; + if (parentPopuproot != null && parentPopuproot.Parent != null) + { + ((Popup)(parentPopuproot.Parent)).Closed += ParentClosed; + } + } _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel); _nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick); } @@ -224,6 +250,11 @@ namespace Avalonia.Controls.Primitives _popupRoot.Show(); + if (ObeyScreenEdges) + { + _popupRoot.SnapInsideScreenEdges(); + } + _ignoreIsOpenChanged = true; IsOpen = true; _ignoreIsOpenChanged = false; @@ -244,6 +275,14 @@ namespace Avalonia.Controls.Primitives var window = _topLevel as Window; if (window != null) window.Deactivated -= WindowDeactivated; + else + { + var parentPopuproot = _topLevel as PopupRoot; + if (parentPopuproot != null && parentPopuproot.Parent != null) + { + ((Popup)parentPopuproot.Parent).Closed -= ParentClosed; + } + } _nonClientListener?.Dispose(); _nonClientListener = null; } @@ -328,8 +367,10 @@ namespace Avalonia.Controls.Primitives /// The popup's position in screen coordinates. protected virtual Point GetPosition() { - return GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, + var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, HorizontalOffset, VerticalOffset); + + return result; } internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset) @@ -381,9 +422,7 @@ namespace Avalonia.Controls.Primitives { if (!StaysOpen) { - var root = ((IVisual)e.Source).GetVisualRoot(); - - if (root != this.PopupRoot) + if (!IsChildOrThis((IVisual)e.Source)) { Close(); e.Handled = true; @@ -391,6 +430,17 @@ namespace Avalonia.Controls.Primitives } } + private bool IsChildOrThis(IVisual child) + { + IVisual root = child.GetVisualRoot(); + while (root is PopupRoot) + { + if (root == PopupRoot) return true; + root = ((PopupRoot)root).Parent.GetVisualRoot(); + } + return false; + } + private void WindowDeactivated(object sender, EventArgs e) { if (!StaysOpen) @@ -398,5 +448,13 @@ namespace Avalonia.Controls.Primitives Close(); } } + + private void ParentClosed(object sender, EventArgs e) + { + if (!StaysOpen) + { + Close(); + } + } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 507a085fed..457a7bd4b4 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -2,10 +2,12 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using Avalonia.Controls.Platform; using Avalonia.Controls.Presenters; using Avalonia.Interactivity; using Avalonia.Layout; +using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; @@ -75,6 +77,30 @@ namespace Avalonia.Controls.Primitives /// public void Dispose() => PlatformImpl?.Dispose(); + /// + /// Moves the Popups position so that it doesnt overlap screen edges. + /// This method can be called immediately after Show has been called. + /// + public void SnapInsideScreenEdges() + { + var window = this.GetSelfAndLogicalAncestors().OfType().First(); + + var screen = window.Screens.ScreenFromPoint(Position); + + var screenX = Position.X + Bounds.Width - screen.Bounds.X; + var screenY = Position.Y + Bounds.Height - screen.Bounds.Y; + + if (screenX > screen.Bounds.Width) + { + Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width)); + } + + if (screenY > screen.Bounds.Height) + { + Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height)); + } + } + /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index c7a77bdf0e..88a9fe077d 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -120,6 +120,7 @@ namespace Avalonia.Controls .Subscribe(_ => { InvalidateFormattedText(); + InvalidateMeasure(); }); } @@ -370,8 +371,6 @@ namespace Avalonia.Controls _constraint = _formattedText.Constraint; _formattedText = null; } - - InvalidateMeasure(); } /// @@ -402,6 +401,7 @@ namespace Avalonia.Controls { base.OnAttachedToLogicalTree(e); InvalidateFormattedText(); + InvalidateMeasure(); } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 34c6b1cfd6..890926db54 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -275,8 +275,11 @@ namespace Avalonia.Controls protected override void OnTextInput(TextInputEventArgs e) { - HandleTextInput(e.Text); - e.Handled = true; + if (!e.Handled) + { + HandleTextInput(e.Text); + e.Handled = true; + } } private void HandleTextInput(string input) diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index c76555e554..17cf681f15 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -59,7 +59,7 @@ namespace Avalonia.Controls.Utils public void UpdateLastState() { - _states.Last.Value = _host.UndoRedoState; + UpdateLastState(_host.UndoRedoState); } public void DiscardRedo() @@ -94,6 +94,7 @@ namespace Avalonia.Controls.Utils public void Clear() { _states.Clear(); + _currentNode = null; } bool WeakTimer.IWeakTimerSubscriber.Tick() diff --git a/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs b/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs index 2d3f978462..555a0b2354 100644 --- a/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs +++ b/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs @@ -86,7 +86,7 @@ namespace Avalonia.Diagnostics.ViewModels private void UpdateFocusedControl() { - _focusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; + FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; } } } diff --git a/src/Avalonia.Input/Cursors.cs b/src/Avalonia.Input/Cursors.cs index e3860e58e5..02a026c998 100644 --- a/src/Avalonia.Input/Cursors.cs +++ b/src/Avalonia.Input/Cursors.cs @@ -38,7 +38,10 @@ namespace Avalonia.Input TopLeftCorner, TopRightCorner, BottomLeftCorner, - BottomRightCorner + BottomRightCorner, + DragMove, + DragCopy, + DragLink, // Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/ // We might enable them later, preferably, by loading pixmax direclty from theme with fallback image diff --git a/src/Avalonia.Input/DataFormats.cs b/src/Avalonia.Input/DataFormats.cs new file mode 100644 index 0000000000..559d2cb643 --- /dev/null +++ b/src/Avalonia.Input/DataFormats.cs @@ -0,0 +1,15 @@ +namespace Avalonia.Input +{ + public static class DataFormats + { + /// + /// Dataformat for plaintext + /// + public static string Text = nameof(Text); + + /// + /// Dataformat for one or more filenames + /// + public static string FileNames = nameof(FileNames); + } +} \ No newline at end of file diff --git a/src/Avalonia.Input/DataObject.cs b/src/Avalonia.Input/DataObject.cs new file mode 100644 index 0000000000..cb642f4d65 --- /dev/null +++ b/src/Avalonia.Input/DataObject.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Input +{ + public class DataObject : IDataObject + { + private readonly Dictionary _items = new Dictionary(); + + public bool Contains(string dataFormat) + { + return _items.ContainsKey(dataFormat); + } + + public object Get(string dataFormat) + { + if (_items.ContainsKey(dataFormat)) + return _items[dataFormat]; + return null; + } + + public IEnumerable GetDataFormats() + { + return _items.Keys; + } + + public IEnumerable GetFileNames() + { + return Get(DataFormats.FileNames) as IEnumerable; + } + + public string GetText() + { + return Get(DataFormats.Text) as string; + } + + public void Set(string dataFormat, object value) + { + _items[dataFormat] = value; + } + } +} diff --git a/src/Avalonia.Input/DragDrop.cs b/src/Avalonia.Input/DragDrop.cs new file mode 100644 index 0000000000..c312732afa --- /dev/null +++ b/src/Avalonia.Input/DragDrop.cs @@ -0,0 +1,54 @@ +using System.Threading.Tasks; +using Avalonia.Interactivity; +using Avalonia.Input.Platform; + +namespace Avalonia.Input +{ + public static class DragDrop + { + /// + /// Event which is raised, when a drag-and-drop operation enters the element. + /// + public static RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); + /// + /// Event which is raised, when a drag-and-drop operation leaves the element. + /// + public static RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); + /// + /// Event which is raised, when a drag-and-drop operation is updated while over the element. + /// + public static RoutedEvent DragOverEvent = RoutedEvent.Register("DragOver", RoutingStrategies.Bubble, typeof(DragDrop)); + /// + /// Event which is raised, when a drag-and-drop operation should complete over the element. + /// + public static RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop)); + + public static AvaloniaProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true); + + /// + /// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation. + /// + public static bool GetAllowDrop(Interactive interactive) + { + return interactive.GetValue(AllowDropProperty); + } + + /// + /// Sets a value indicating whether the given interactive can be used as the target of a drag-and-drop operation. + /// + public static void SetAllowDrop(Interactive interactive, bool value) + { + interactive.SetValue(AllowDropProperty, value); + } + + /// + /// Starts a dragging operation with the given and returns the applied drop effect from the target. + /// + /// + public static Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects) + { + var src = AvaloniaLocator.Current.GetService(); + return src?.DoDragDrop(data, allowedEffects) ?? Task.FromResult(DragDropEffects.None); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Input/DragDropDevice.cs b/src/Avalonia.Input/DragDropDevice.cs new file mode 100644 index 0000000000..2615e3a212 --- /dev/null +++ b/src/Avalonia.Input/DragDropDevice.cs @@ -0,0 +1,111 @@ +using Avalonia.Interactivity; +using Avalonia.VisualTree; +using System.Linq; +using Avalonia.Input.Raw; + +namespace Avalonia.Input +{ + public class DragDropDevice : IDragDropDevice + { + public static readonly DragDropDevice Instance = new DragDropDevice(); + + private Interactive _lastTarget = null; + + private Interactive GetTarget(IInputElement root, Point local) + { + var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault(); + if (target != null && DragDrop.GetAllowDrop(target)) + return target; + return null; + } + + private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data) + { + if (target == null) + return DragDropEffects.None; + var args = new DragEventArgs(routedEvent, data) + { + RoutedEvent = routedEvent, + DragEffects = operation + }; + target.RaiseEvent(args); + return args.DragEffects; + } + + private DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) + { + _lastTarget = GetTarget(inputRoot, point); + return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data); + } + + private DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) + { + var target = GetTarget(inputRoot, point); + + if (target == _lastTarget) + return RaiseDragEvent(target, DragDrop.DragOverEvent, effects, data); + + try + { + if (_lastTarget != null) + _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + return RaiseDragEvent(target, DragDrop.DragEnterEvent, effects, data); + } + finally + { + _lastTarget = target; + } + } + + private void DragLeave(IInputElement inputRoot) + { + if (_lastTarget == null) + return; + try + { + _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + } + finally + { + _lastTarget = null; + } + } + + private DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) + { + try + { + return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, effects, data); + } + finally + { + _lastTarget = null; + } + } + + public void ProcessRawEvent(RawInputEventArgs e) + { + if (!e.Handled && e is RawDragEvent margs) + ProcessRawEvent(margs); + } + + private void ProcessRawEvent(RawDragEvent e) + { + switch (e.Type) + { + case RawDragEventType.DragEnter: + e.Effects = DragEnter(e.InputRoot, e.Location, e.Data, e.Effects); + break; + case RawDragEventType.DragOver: + e.Effects = DragOver(e.InputRoot, e.Location, e.Data, e.Effects); + break; + case RawDragEventType.DragLeave: + DragLeave(e.InputRoot); + break; + case RawDragEventType.Drop: + e.Effects = Drop(e.InputRoot, e.Location, e.Data, e.Effects); + break; + } + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Input/DragDropEffects.cs b/src/Avalonia.Input/DragDropEffects.cs new file mode 100644 index 0000000000..bcda1091d5 --- /dev/null +++ b/src/Avalonia.Input/DragDropEffects.cs @@ -0,0 +1,13 @@ +using System; + +namespace Avalonia.Input +{ + [Flags] + public enum DragDropEffects + { + None = 0, + Copy = 1, + Move = 2, + Link = 4, + } +} \ No newline at end of file diff --git a/src/Avalonia.Input/DragEventArgs.cs b/src/Avalonia.Input/DragEventArgs.cs new file mode 100644 index 0000000000..12d5a8941e --- /dev/null +++ b/src/Avalonia.Input/DragEventArgs.cs @@ -0,0 +1,18 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class DragEventArgs : RoutedEventArgs + { + public DragDropEffects DragEffects { get; set; } + + public IDataObject Data { get; private set; } + + public DragEventArgs(RoutedEvent routedEvent, IDataObject data) + : base(routedEvent) + { + this.Data = data; + } + + } +} \ No newline at end of file diff --git a/src/Avalonia.Input/IDataObject.cs b/src/Avalonia.Input/IDataObject.cs new file mode 100644 index 0000000000..1b12323d99 --- /dev/null +++ b/src/Avalonia.Input/IDataObject.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace Avalonia.Input +{ + /// + /// Interface to access information about the data of a drag-and-drop operation. + /// + public interface IDataObject + { + /// + /// Lists all formats which are present in the DataObject. + /// + /// + IEnumerable GetDataFormats(); + + /// + /// Checks wether a given DataFormat is present in this object + /// + /// + bool Contains(string dataFormat); + + /// + /// Returns the dragged text if the DataObject contains any text. + /// + /// + string GetText(); + + /// + /// Returns a list of filenames if the DataObject contains filenames. + /// + /// + IEnumerable GetFileNames(); + + /// + /// Tries to get the data of the given DataFormat. + /// + object Get(string dataFormat); + } +} \ No newline at end of file diff --git a/src/Avalonia.Input/Platform/IPlatformDragSource.cs b/src/Avalonia.Input/Platform/IPlatformDragSource.cs new file mode 100644 index 0000000000..669251c50b --- /dev/null +++ b/src/Avalonia.Input/Platform/IPlatformDragSource.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Input; +using Avalonia.Interactivity; + +namespace Avalonia.Input.Platform +{ + public interface IPlatformDragSource + { + Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects); + } +} diff --git a/src/Avalonia.Input/Raw/IDragDropDevice.cs b/src/Avalonia.Input/Raw/IDragDropDevice.cs new file mode 100644 index 0000000000..6aab1b868d --- /dev/null +++ b/src/Avalonia.Input/Raw/IDragDropDevice.cs @@ -0,0 +1,8 @@ +using Avalonia.Input; + +namespace Avalonia.Input.Raw +{ + public interface IDragDropDevice : IInputDevice + { + } +} \ No newline at end of file diff --git a/src/Avalonia.Input/Raw/RawDragEvent.cs b/src/Avalonia.Input/Raw/RawDragEvent.cs new file mode 100644 index 0000000000..49125b4c07 --- /dev/null +++ b/src/Avalonia.Input/Raw/RawDragEvent.cs @@ -0,0 +1,26 @@ +using System; +using Avalonia.Input; +using Avalonia.Input.Raw; + +namespace Avalonia.Input.Raw +{ + public class RawDragEvent : RawInputEventArgs + { + public IInputElement InputRoot { get; } + public Point Location { get; } + public IDataObject Data { get; } + public DragDropEffects Effects { get; set; } + public RawDragEventType Type { get; } + + public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type, + IInputElement inputRoot, Point location, IDataObject data, DragDropEffects effects) + :base(inputDevice, 0) + { + Type = type; + InputRoot = inputRoot; + Location = location; + Data = data; + Effects = effects; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Input/Raw/RawDragEventType.cs b/src/Avalonia.Input/Raw/RawDragEventType.cs new file mode 100644 index 0000000000..9635f77467 --- /dev/null +++ b/src/Avalonia.Input/Raw/RawDragEventType.cs @@ -0,0 +1,10 @@ +namespace Avalonia.Input.Raw +{ + public enum RawDragEventType + { + DragEnter, + DragOver, + DragLeave, + Drop + } +} \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 66f226d2f6..efb31175fa 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -45,7 +45,8 @@ + IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}" + ObeyScreenEdges="True"> @@ -92,7 +93,8 @@ + StaysOpen="True" + ObeyScreenEdges="True"> diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 4e5cd61994..70804ee04f 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Utilities; using System; using System.Globalization; using System.Linq; @@ -317,23 +318,16 @@ namespace Avalonia /// The . public static Matrix Parse(string s, CultureInfo culture) { - var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .ToArray(); - - if (parts.Length == 6) + using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Matrix")) { return new Matrix( - double.Parse(parts[0], culture), - double.Parse(parts[1], culture), - double.Parse(parts[2], culture), - double.Parse(parts[3], culture), - double.Parse(parts[4], culture), - double.Parse(parts[5], culture)); - } - else - { - throw new FormatException("Invalid Matrix."); + tokenizer.ReadDouble(), + tokenizer.ReadDouble(), + tokenizer.ReadDouble(), + tokenizer.ReadDouble(), + tokenizer.ReadDouble(), + tokenizer.ReadDouble() + ); } } } diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index 40ac24b605..d6b0e43cdc 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -34,26 +34,21 @@ namespace Avalonia.Media /// The . public static IBrush Parse(string s) { + Contract.Requires(s != null); + Contract.Requires(s.Length > 0); + if (s[0] == '#') { return new SolidColorBrush(Color.Parse(s)); } - else - { - var upper = s.ToUpperInvariant(); - var member = typeof(Brushes).GetTypeInfo().DeclaredProperties - .FirstOrDefault(x => x.Name.ToUpperInvariant() == upper); - if (member != null) - { - var brush = (ISolidColorBrush)member.GetValue(null); - return new SolidColorBrush(brush.Color, brush.Opacity); - } - else - { - throw new FormatException($"Invalid brush string: '{s}'."); - } + var brush = KnownColors.GetKnownBrush(s); + if (brush != null) + { + return brush; } + + throw new FormatException($"Invalid brush string: '{s}'."); } } } diff --git a/src/Avalonia.Visuals/Media/Brushes.cs b/src/Avalonia.Visuals/Media/Brushes.cs index 4c89c97b49..83ff043397 100644 --- a/src/Avalonia.Visuals/Media/Brushes.cs +++ b/src/Avalonia.Visuals/Media/Brushes.cs @@ -1,8 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Media.Immutable; - namespace Avalonia.Media { /// @@ -10,857 +8,709 @@ namespace Avalonia.Media /// public static class Brushes { - /// - /// Initializes static members of the class. - /// - static Brushes() - { - AliceBlue = new ImmutableSolidColorBrush(Colors.AliceBlue); - AntiqueWhite = new ImmutableSolidColorBrush(Colors.AntiqueWhite); - Aqua = new ImmutableSolidColorBrush(Colors.Aqua); - Aquamarine = new ImmutableSolidColorBrush(Colors.Aquamarine); - Azure = new ImmutableSolidColorBrush(Colors.Azure); - Beige = new ImmutableSolidColorBrush(Colors.Beige); - Bisque = new ImmutableSolidColorBrush(Colors.Bisque); - Black = new ImmutableSolidColorBrush(Colors.Black); - BlanchedAlmond = new ImmutableSolidColorBrush(Colors.BlanchedAlmond); - Blue = new ImmutableSolidColorBrush(Colors.Blue); - BlueViolet = new ImmutableSolidColorBrush(Colors.BlueViolet); - Brown = new ImmutableSolidColorBrush(Colors.Brown); - BurlyWood = new ImmutableSolidColorBrush(Colors.BurlyWood); - CadetBlue = new ImmutableSolidColorBrush(Colors.CadetBlue); - Chartreuse = new ImmutableSolidColorBrush(Colors.Chartreuse); - Chocolate = new ImmutableSolidColorBrush(Colors.Chocolate); - Coral = new ImmutableSolidColorBrush(Colors.Coral); - CornflowerBlue = new ImmutableSolidColorBrush(Colors.CornflowerBlue); - Cornsilk = new ImmutableSolidColorBrush(Colors.Cornsilk); - Crimson = new ImmutableSolidColorBrush(Colors.Crimson); - Cyan = new ImmutableSolidColorBrush(Colors.Cyan); - DarkBlue = new ImmutableSolidColorBrush(Colors.DarkBlue); - DarkCyan = new ImmutableSolidColorBrush(Colors.DarkCyan); - DarkGoldenrod = new ImmutableSolidColorBrush(Colors.DarkGoldenrod); - DarkGray = new ImmutableSolidColorBrush(Colors.DarkGray); - DarkGreen = new ImmutableSolidColorBrush(Colors.DarkGreen); - DarkKhaki = new ImmutableSolidColorBrush(Colors.DarkKhaki); - DarkMagenta = new ImmutableSolidColorBrush(Colors.DarkMagenta); - DarkOliveGreen = new ImmutableSolidColorBrush(Colors.DarkOliveGreen); - DarkOrange = new ImmutableSolidColorBrush(Colors.DarkOrange); - DarkOrchid = new ImmutableSolidColorBrush(Colors.DarkOrchid); - DarkRed = new ImmutableSolidColorBrush(Colors.DarkRed); - DarkSalmon = new ImmutableSolidColorBrush(Colors.DarkSalmon); - DarkSeaGreen = new ImmutableSolidColorBrush(Colors.DarkSeaGreen); - DarkSlateBlue = new ImmutableSolidColorBrush(Colors.DarkSlateBlue); - DarkSlateGray = new ImmutableSolidColorBrush(Colors.DarkSlateGray); - DarkTurquoise = new ImmutableSolidColorBrush(Colors.DarkTurquoise); - DarkViolet = new ImmutableSolidColorBrush(Colors.DarkViolet); - DeepPink = new ImmutableSolidColorBrush(Colors.DeepPink); - DeepSkyBlue = new ImmutableSolidColorBrush(Colors.DeepSkyBlue); - DimGray = new ImmutableSolidColorBrush(Colors.DimGray); - DodgerBlue = new ImmutableSolidColorBrush(Colors.DodgerBlue); - Firebrick = new ImmutableSolidColorBrush(Colors.Firebrick); - FloralWhite = new ImmutableSolidColorBrush(Colors.FloralWhite); - ForestGreen = new ImmutableSolidColorBrush(Colors.ForestGreen); - Fuchsia = new ImmutableSolidColorBrush(Colors.Fuchsia); - Gainsboro = new ImmutableSolidColorBrush(Colors.Gainsboro); - GhostWhite = new ImmutableSolidColorBrush(Colors.GhostWhite); - Gold = new ImmutableSolidColorBrush(Colors.Gold); - Goldenrod = new ImmutableSolidColorBrush(Colors.Goldenrod); - Gray = new ImmutableSolidColorBrush(Colors.Gray); - Green = new ImmutableSolidColorBrush(Colors.Green); - GreenYellow = new ImmutableSolidColorBrush(Colors.GreenYellow); - Honeydew = new ImmutableSolidColorBrush(Colors.Honeydew); - HotPink = new ImmutableSolidColorBrush(Colors.HotPink); - IndianRed = new ImmutableSolidColorBrush(Colors.IndianRed); - Indigo = new ImmutableSolidColorBrush(Colors.Indigo); - Ivory = new ImmutableSolidColorBrush(Colors.Ivory); - Khaki = new ImmutableSolidColorBrush(Colors.Khaki); - Lavender = new ImmutableSolidColorBrush(Colors.Lavender); - LavenderBlush = new ImmutableSolidColorBrush(Colors.LavenderBlush); - LawnGreen = new ImmutableSolidColorBrush(Colors.LawnGreen); - LemonChiffon = new ImmutableSolidColorBrush(Colors.LemonChiffon); - LightBlue = new ImmutableSolidColorBrush(Colors.LightBlue); - LightCoral = new ImmutableSolidColorBrush(Colors.LightCoral); - LightCyan = new ImmutableSolidColorBrush(Colors.LightCyan); - LightGoldenrodYellow = new ImmutableSolidColorBrush(Colors.LightGoldenrodYellow); - LightGray = new ImmutableSolidColorBrush(Colors.LightGray); - LightGreen = new ImmutableSolidColorBrush(Colors.LightGreen); - LightPink = new ImmutableSolidColorBrush(Colors.LightPink); - LightSalmon = new ImmutableSolidColorBrush(Colors.LightSalmon); - LightSeaGreen = new ImmutableSolidColorBrush(Colors.LightSeaGreen); - LightSkyBlue = new ImmutableSolidColorBrush(Colors.LightSkyBlue); - LightSlateGray = new ImmutableSolidColorBrush(Colors.LightSlateGray); - LightSteelBlue = new ImmutableSolidColorBrush(Colors.LightSteelBlue); - LightYellow = new ImmutableSolidColorBrush(Colors.LightYellow); - Lime = new ImmutableSolidColorBrush(Colors.Lime); - LimeGreen = new ImmutableSolidColorBrush(Colors.LimeGreen); - Linen = new ImmutableSolidColorBrush(Colors.Linen); - Magenta = new ImmutableSolidColorBrush(Colors.Magenta); - Maroon = new ImmutableSolidColorBrush(Colors.Maroon); - MediumAquamarine = new ImmutableSolidColorBrush(Colors.MediumAquamarine); - MediumBlue = new ImmutableSolidColorBrush(Colors.MediumBlue); - MediumOrchid = new ImmutableSolidColorBrush(Colors.MediumOrchid); - MediumPurple = new ImmutableSolidColorBrush(Colors.MediumPurple); - MediumSeaGreen = new ImmutableSolidColorBrush(Colors.MediumSeaGreen); - MediumSlateBlue = new ImmutableSolidColorBrush(Colors.MediumSlateBlue); - MediumSpringGreen = new ImmutableSolidColorBrush(Colors.MediumSpringGreen); - MediumTurquoise = new ImmutableSolidColorBrush(Colors.MediumTurquoise); - MediumVioletRed = new ImmutableSolidColorBrush(Colors.MediumVioletRed); - MidnightBlue = new ImmutableSolidColorBrush(Colors.MidnightBlue); - MintCream = new ImmutableSolidColorBrush(Colors.MintCream); - MistyRose = new ImmutableSolidColorBrush(Colors.MistyRose); - Moccasin = new ImmutableSolidColorBrush(Colors.Moccasin); - NavajoWhite = new ImmutableSolidColorBrush(Colors.NavajoWhite); - Navy = new ImmutableSolidColorBrush(Colors.Navy); - OldLace = new ImmutableSolidColorBrush(Colors.OldLace); - Olive = new ImmutableSolidColorBrush(Colors.Olive); - OliveDrab = new ImmutableSolidColorBrush(Colors.OliveDrab); - Orange = new ImmutableSolidColorBrush(Colors.Orange); - OrangeRed = new ImmutableSolidColorBrush(Colors.OrangeRed); - Orchid = new ImmutableSolidColorBrush(Colors.Orchid); - PaleGoldenrod = new ImmutableSolidColorBrush(Colors.PaleGoldenrod); - PaleGreen = new ImmutableSolidColorBrush(Colors.PaleGreen); - PaleTurquoise = new ImmutableSolidColorBrush(Colors.PaleTurquoise); - PaleVioletRed = new ImmutableSolidColorBrush(Colors.PaleVioletRed); - PapayaWhip = new ImmutableSolidColorBrush(Colors.PapayaWhip); - PeachPuff = new ImmutableSolidColorBrush(Colors.PeachPuff); - Peru = new ImmutableSolidColorBrush(Colors.Peru); - Pink = new ImmutableSolidColorBrush(Colors.Pink); - Plum = new ImmutableSolidColorBrush(Colors.Plum); - PowderBlue = new ImmutableSolidColorBrush(Colors.PowderBlue); - Purple = new ImmutableSolidColorBrush(Colors.Purple); - Red = new ImmutableSolidColorBrush(Colors.Red); - RosyBrown = new ImmutableSolidColorBrush(Colors.RosyBrown); - RoyalBlue = new ImmutableSolidColorBrush(Colors.RoyalBlue); - SaddleBrown = new ImmutableSolidColorBrush(Colors.SaddleBrown); - Salmon = new ImmutableSolidColorBrush(Colors.Salmon); - SandyBrown = new ImmutableSolidColorBrush(Colors.SandyBrown); - SeaGreen = new ImmutableSolidColorBrush(Colors.SeaGreen); - SeaShell = new ImmutableSolidColorBrush(Colors.SeaShell); - Sienna = new ImmutableSolidColorBrush(Colors.Sienna); - Silver = new ImmutableSolidColorBrush(Colors.Silver); - SkyBlue = new ImmutableSolidColorBrush(Colors.SkyBlue); - SlateBlue = new ImmutableSolidColorBrush(Colors.SlateBlue); - SlateGray = new ImmutableSolidColorBrush(Colors.SlateGray); - Snow = new ImmutableSolidColorBrush(Colors.Snow); - SpringGreen = new ImmutableSolidColorBrush(Colors.SpringGreen); - SteelBlue = new ImmutableSolidColorBrush(Colors.SteelBlue); - Tan = new ImmutableSolidColorBrush(Colors.Tan); - Teal = new ImmutableSolidColorBrush(Colors.Teal); - Thistle = new ImmutableSolidColorBrush(Colors.Thistle); - Tomato = new ImmutableSolidColorBrush(Colors.Tomato); - Transparent = new ImmutableSolidColorBrush(Colors.Transparent); - Turquoise = new ImmutableSolidColorBrush(Colors.Turquoise); - Violet = new ImmutableSolidColorBrush(Colors.Violet); - Wheat = new ImmutableSolidColorBrush(Colors.Wheat); - White = new ImmutableSolidColorBrush(Colors.White); - WhiteSmoke = new ImmutableSolidColorBrush(Colors.WhiteSmoke); - Yellow = new ImmutableSolidColorBrush(Colors.Yellow); - YellowGreen = new ImmutableSolidColorBrush(Colors.YellowGreen); - } - /// /// Gets an colored brush. /// - public static ISolidColorBrush AliceBlue { get; private set; } + public static ISolidColorBrush AliceBlue => KnownColor.AliceBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush AntiqueWhite { get; private set; } + public static ISolidColorBrush AntiqueWhite => KnownColor.AntiqueWhite.ToBrush(); /// - /// Gets an colored brush. + /// Gets an colored brush. /// - public static ISolidColorBrush Aqua { get; private set; } + public static ISolidColorBrush Aqua => KnownColor.Aqua.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Aquamarine { get; private set; } + public static ISolidColorBrush Aquamarine => KnownColor.Aquamarine.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Azure { get; private set; } + public static ISolidColorBrush Azure => KnownColor.Azure.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Beige { get; private set; } + public static ISolidColorBrush Beige => KnownColor.Beige.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Bisque { get; private set; } + public static ISolidColorBrush Bisque => KnownColor.Bisque.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Black { get; private set; } + public static ISolidColorBrush Black => KnownColor.Black.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush BlanchedAlmond { get; private set; } + public static ISolidColorBrush BlanchedAlmond => KnownColor.BlanchedAlmond.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Blue { get; private set; } + public static ISolidColorBrush Blue => KnownColor.Blue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush BlueViolet { get; private set; } + public static ISolidColorBrush BlueViolet => KnownColor.BlueViolet.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Brown { get; private set; } + public static ISolidColorBrush Brown => KnownColor.Brown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush BurlyWood { get; private set; } + public static ISolidColorBrush BurlyWood => KnownColor.BurlyWood.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush CadetBlue { get; private set; } + public static ISolidColorBrush CadetBlue => KnownColor.CadetBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Chartreuse { get; private set; } + public static ISolidColorBrush Chartreuse => KnownColor.Chartreuse.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Chocolate { get; private set; } + public static ISolidColorBrush Chocolate => KnownColor.Chocolate.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Coral { get; private set; } + public static ISolidColorBrush Coral => KnownColor.Coral.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush CornflowerBlue { get; private set; } + public static ISolidColorBrush CornflowerBlue => KnownColor.CornflowerBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Cornsilk { get; private set; } + public static ISolidColorBrush Cornsilk => KnownColor.Cornsilk.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Crimson { get; private set; } + public static ISolidColorBrush Crimson => KnownColor.Crimson.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Cyan { get; private set; } + public static ISolidColorBrush Cyan => KnownColor.Cyan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkBlue { get; private set; } + public static ISolidColorBrush DarkBlue => KnownColor.DarkBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkCyan { get; private set; } + public static ISolidColorBrush DarkCyan => KnownColor.DarkCyan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkGoldenrod { get; private set; } + public static ISolidColorBrush DarkGoldenrod => KnownColor.DarkGoldenrod.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkGray { get; private set; } + public static ISolidColorBrush DarkGray => KnownColor.DarkGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkGreen { get; private set; } + public static ISolidColorBrush DarkGreen => KnownColor.DarkGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkKhaki { get; private set; } + public static ISolidColorBrush DarkKhaki => KnownColor.DarkKhaki.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkMagenta { get; private set; } + public static ISolidColorBrush DarkMagenta => KnownColor.DarkMagenta.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkOliveGreen { get; private set; } + public static ISolidColorBrush DarkOliveGreen => KnownColor.DarkOliveGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkOrange { get; private set; } + public static ISolidColorBrush DarkOrange => KnownColor.DarkOrange.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkOrchid { get; private set; } + public static ISolidColorBrush DarkOrchid => KnownColor.DarkOrchid.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkRed { get; private set; } + public static ISolidColorBrush DarkRed => KnownColor.DarkRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSalmon { get; private set; } + public static ISolidColorBrush DarkSalmon => KnownColor.DarkSalmon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSeaGreen { get; private set; } + public static ISolidColorBrush DarkSeaGreen => KnownColor.DarkSeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSlateBlue { get; private set; } + public static ISolidColorBrush DarkSlateBlue => KnownColor.DarkSlateBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSlateGray { get; private set; } + public static ISolidColorBrush DarkSlateGray => KnownColor.DarkSlateGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkTurquoise { get; private set; } + public static ISolidColorBrush DarkTurquoise => KnownColor.DarkTurquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkViolet { get; private set; } + public static ISolidColorBrush DarkViolet => KnownColor.DarkViolet.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DeepPink { get; private set; } + public static ISolidColorBrush DeepPink => KnownColor.DeepPink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DeepSkyBlue { get; private set; } + public static ISolidColorBrush DeepSkyBlue => KnownColor.DeepSkyBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DimGray { get; private set; } + public static ISolidColorBrush DimGray => KnownColor.DimGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DodgerBlue { get; private set; } + public static ISolidColorBrush DodgerBlue => KnownColor.DodgerBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Firebrick { get; private set; } + public static ISolidColorBrush Firebrick => KnownColor.Firebrick.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush FloralWhite { get; private set; } + public static ISolidColorBrush FloralWhite => KnownColor.FloralWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush ForestGreen { get; private set; } + public static ISolidColorBrush ForestGreen => KnownColor.ForestGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Fuchsia { get; private set; } + public static ISolidColorBrush Fuchsia => KnownColor.Fuchsia.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Gainsboro { get; private set; } + public static ISolidColorBrush Gainsboro => KnownColor.Gainsboro.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush GhostWhite { get; private set; } + public static ISolidColorBrush GhostWhite => KnownColor.GhostWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Gold { get; private set; } + public static ISolidColorBrush Gold => KnownColor.Gold.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Goldenrod { get; private set; } + public static ISolidColorBrush Goldenrod => KnownColor.Goldenrod.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Gray { get; private set; } + public static ISolidColorBrush Gray => KnownColor.Gray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Green { get; private set; } + public static ISolidColorBrush Green => KnownColor.Green.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush GreenYellow { get; private set; } + public static ISolidColorBrush GreenYellow => KnownColor.GreenYellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Honeydew { get; private set; } + public static ISolidColorBrush Honeydew => KnownColor.Honeydew.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush HotPink { get; private set; } + public static ISolidColorBrush HotPink => KnownColor.HotPink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush IndianRed { get; private set; } + public static ISolidColorBrush IndianRed => KnownColor.IndianRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Indigo { get; private set; } + public static ISolidColorBrush Indigo => KnownColor.Indigo.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Ivory { get; private set; } + public static ISolidColorBrush Ivory => KnownColor.Ivory.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Khaki { get; private set; } + public static ISolidColorBrush Khaki => KnownColor.Khaki.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Lavender { get; private set; } + public static ISolidColorBrush Lavender => KnownColor.Lavender.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LavenderBlush { get; private set; } + public static ISolidColorBrush LavenderBlush => KnownColor.LavenderBlush.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LawnGreen { get; private set; } + public static ISolidColorBrush LawnGreen => KnownColor.LawnGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LemonChiffon { get; private set; } + public static ISolidColorBrush LemonChiffon => KnownColor.LemonChiffon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightBlue { get; private set; } + public static ISolidColorBrush LightBlue => KnownColor.LightBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightCoral { get; private set; } + public static ISolidColorBrush LightCoral => KnownColor.LightCoral.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightCyan { get; private set; } + public static ISolidColorBrush LightCyan => KnownColor.LightCyan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightGoldenrodYellow { get; private set; } + public static ISolidColorBrush LightGoldenrodYellow => KnownColor.LightGoldenrodYellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightGray { get; private set; } + public static ISolidColorBrush LightGray => KnownColor.LightGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightGreen { get; private set; } + public static ISolidColorBrush LightGreen => KnownColor.LightGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightPink { get; private set; } + public static ISolidColorBrush LightPink => KnownColor.LightPink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSalmon { get; private set; } + public static ISolidColorBrush LightSalmon => KnownColor.LightSalmon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSeaGreen { get; private set; } + public static ISolidColorBrush LightSeaGreen => KnownColor.LightSeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSkyBlue { get; private set; } + public static ISolidColorBrush LightSkyBlue => KnownColor.LightSkyBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSlateGray { get; private set; } + public static ISolidColorBrush LightSlateGray => KnownColor.LightSlateGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSteelBlue { get; private set; } + public static ISolidColorBrush LightSteelBlue => KnownColor.LightSteelBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightYellow { get; private set; } + public static ISolidColorBrush LightYellow => KnownColor.LightYellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Lime { get; private set; } + public static ISolidColorBrush Lime => KnownColor.Lime.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LimeGreen { get; private set; } + public static ISolidColorBrush LimeGreen => KnownColor.LimeGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Linen { get; private set; } + public static ISolidColorBrush Linen => KnownColor.Linen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Magenta { get; private set; } + public static ISolidColorBrush Magenta => KnownColor.Magenta.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Maroon { get; private set; } + public static ISolidColorBrush Maroon => KnownColor.Maroon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumAquamarine { get; private set; } + public static ISolidColorBrush MediumAquamarine => KnownColor.MediumAquamarine.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumBlue { get; private set; } + public static ISolidColorBrush MediumBlue => KnownColor.MediumBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumOrchid { get; private set; } + public static ISolidColorBrush MediumOrchid => KnownColor.MediumOrchid.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumPurple { get; private set; } + public static ISolidColorBrush MediumPurple => KnownColor.MediumPurple.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumSeaGreen { get; private set; } + public static ISolidColorBrush MediumSeaGreen => KnownColor.MediumSeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumSlateBlue { get; private set; } + public static ISolidColorBrush MediumSlateBlue => KnownColor.MediumSlateBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumSpringGreen { get; private set; } + public static ISolidColorBrush MediumSpringGreen => KnownColor.MediumSpringGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumTurquoise { get; private set; } + public static ISolidColorBrush MediumTurquoise => KnownColor.MediumTurquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumVioletRed { get; private set; } + public static ISolidColorBrush MediumVioletRed => KnownColor.MediumVioletRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MidnightBlue { get; private set; } + public static ISolidColorBrush MidnightBlue => KnownColor.MidnightBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MintCream { get; private set; } + public static ISolidColorBrush MintCream => KnownColor.MintCream.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MistyRose { get; private set; } + public static ISolidColorBrush MistyRose => KnownColor.MistyRose.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Moccasin { get; private set; } + public static ISolidColorBrush Moccasin => KnownColor.Moccasin.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush NavajoWhite { get; private set; } + public static ISolidColorBrush NavajoWhite => KnownColor.NavajoWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Navy { get; private set; } + public static ISolidColorBrush Navy => KnownColor.Navy.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush OldLace { get; private set; } + public static ISolidColorBrush OldLace => KnownColor.OldLace.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Olive { get; private set; } + public static ISolidColorBrush Olive => KnownColor.Olive.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush OliveDrab { get; private set; } + public static ISolidColorBrush OliveDrab => KnownColor.OliveDrab.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Orange { get; private set; } + public static ISolidColorBrush Orange => KnownColor.Orange.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush OrangeRed { get; private set; } + public static ISolidColorBrush OrangeRed => KnownColor.OrangeRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Orchid { get; private set; } + public static ISolidColorBrush Orchid => KnownColor.Orchid.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleGoldenrod { get; private set; } + public static ISolidColorBrush PaleGoldenrod => KnownColor.PaleGoldenrod.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleGreen { get; private set; } + public static ISolidColorBrush PaleGreen => KnownColor.PaleGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleTurquoise { get; private set; } + public static ISolidColorBrush PaleTurquoise => KnownColor.PaleTurquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleVioletRed { get; private set; } + public static ISolidColorBrush PaleVioletRed => KnownColor.PaleVioletRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PapayaWhip { get; private set; } + public static ISolidColorBrush PapayaWhip => KnownColor.PapayaWhip.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PeachPuff { get; private set; } + public static ISolidColorBrush PeachPuff => KnownColor.PeachPuff.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Peru { get; private set; } + public static ISolidColorBrush Peru => KnownColor.Peru.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Pink { get; private set; } + public static ISolidColorBrush Pink => KnownColor.Pink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Plum { get; private set; } + public static ISolidColorBrush Plum => KnownColor.Plum.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PowderBlue { get; private set; } + public static ISolidColorBrush PowderBlue => KnownColor.PowderBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Purple { get; private set; } + public static ISolidColorBrush Purple => KnownColor.Purple.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Red { get; private set; } + public static ISolidColorBrush Red => KnownColor.Red.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush RosyBrown { get; private set; } + public static ISolidColorBrush RosyBrown => KnownColor.RosyBrown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush RoyalBlue { get; private set; } + public static ISolidColorBrush RoyalBlue => KnownColor.RoyalBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SaddleBrown { get; private set; } + public static ISolidColorBrush SaddleBrown => KnownColor.SaddleBrown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Salmon { get; private set; } + public static ISolidColorBrush Salmon => KnownColor.Salmon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SandyBrown { get; private set; } + public static ISolidColorBrush SandyBrown => KnownColor.SandyBrown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SeaGreen { get; private set; } + public static ISolidColorBrush SeaGreen => KnownColor.SeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SeaShell { get; private set; } + public static ISolidColorBrush SeaShell => KnownColor.SeaShell.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Sienna { get; private set; } + public static ISolidColorBrush Sienna => KnownColor.Sienna.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Silver { get; private set; } + public static ISolidColorBrush Silver => KnownColor.Silver.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SkyBlue { get; private set; } + public static ISolidColorBrush SkyBlue => KnownColor.SkyBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SlateBlue { get; private set; } + public static ISolidColorBrush SlateBlue => KnownColor.SlateBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SlateGray { get; private set; } + public static ISolidColorBrush SlateGray => KnownColor.SlateGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Snow { get; private set; } + public static ISolidColorBrush Snow => KnownColor.Snow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SpringGreen { get; private set; } + public static ISolidColorBrush SpringGreen => KnownColor.SpringGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SteelBlue { get; private set; } + public static ISolidColorBrush SteelBlue => KnownColor.SteelBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Tan { get; private set; } + public static ISolidColorBrush Tan => KnownColor.Tan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Teal { get; private set; } + public static ISolidColorBrush Teal => KnownColor.Teal.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Thistle { get; private set; } + public static ISolidColorBrush Thistle => KnownColor.Thistle.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Tomato { get; private set; } + public static ISolidColorBrush Tomato => KnownColor.Tomato.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Transparent { get; private set; } + public static ISolidColorBrush Transparent => KnownColor.Transparent.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Turquoise { get; private set; } + public static ISolidColorBrush Turquoise => KnownColor.Turquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Violet { get; private set; } + public static ISolidColorBrush Violet => KnownColor.Violet.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Wheat { get; private set; } + public static ISolidColorBrush Wheat => KnownColor.Wheat.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush White { get; private set; } + public static ISolidColorBrush White => KnownColor.White.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush WhiteSmoke { get; private set; } + public static ISolidColorBrush WhiteSmoke => KnownColor.WhiteSmoke.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Yellow { get; private set; } + public static ISolidColorBrush Yellow => KnownColor.Yellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush YellowGreen { get; private set; } + public static ISolidColorBrush YellowGreen => KnownColor.YellowGreen.ToBrush(); } } diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs index cbf5a86fd6..82cc19347a 100644 --- a/src/Avalonia.Visuals/Media/Color.cs +++ b/src/Avalonia.Visuals/Media/Color.cs @@ -88,6 +88,9 @@ namespace Avalonia.Media /// The . public static Color Parse(string s) { + if (s == null) throw new ArgumentNullException(nameof(s)); + if (s.Length == 0) throw new FormatException(); + if (s[0] == '#') { var or = 0u; @@ -103,21 +106,15 @@ namespace Avalonia.Media return FromUInt32(uint.Parse(s.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture) | or); } - else - { - var upper = s.ToUpperInvariant(); - var member = typeof(Colors).GetTypeInfo().DeclaredProperties - .FirstOrDefault(x => x.Name.ToUpperInvariant() == upper); - if (member != null) - { - return (Color)member.GetValue(null); - } - else - { - throw new FormatException($"Invalid color string: '{s}'."); - } + var knownColor = KnownColors.GetKnownColor(s); + + if (knownColor != KnownColor.None) + { + return knownColor.ToColor(); } + + throw new FormatException($"Invalid color string: '{s}'."); } /// @@ -128,8 +125,8 @@ namespace Avalonia.Media /// public override string ToString() { - uint rgb = ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B; - return $"#{rgb:x8}"; + uint rgb = ToUint32(); + return KnownColors.GetKnownColorName(rgb) ?? $"#{rgb:x8}"; } /// diff --git a/src/Avalonia.Visuals/Media/Colors.cs b/src/Avalonia.Visuals/Media/Colors.cs index 7296d7dd39..1067cf66aa 100644 --- a/src/Avalonia.Visuals/Media/Colors.cs +++ b/src/Avalonia.Visuals/Media/Colors.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Linq; + namespace Avalonia.Media { /// @@ -11,706 +13,706 @@ namespace Avalonia.Media /// /// Gets a color with an ARGB value of #fff0f8ff. /// - public static Color AliceBlue => Color.FromUInt32(0xfff0f8ff); + public static Color AliceBlue => KnownColor.AliceBlue.ToColor(); /// /// Gets a color with an ARGB value of #fffaebd7. /// - public static Color AntiqueWhite => Color.FromUInt32(0xfffaebd7); + public static Color AntiqueWhite => KnownColor.AntiqueWhite.ToColor(); /// /// Gets a color with an ARGB value of #ff00ffff. /// - public static Color Aqua => Color.FromUInt32(0xff00ffff); + public static Color Aqua => KnownColor.Aqua.ToColor(); /// /// Gets a color with an ARGB value of #ff7fffd4. /// - public static Color Aquamarine => Color.FromUInt32(0xff7fffd4); + public static Color Aquamarine => KnownColor.Aquamarine.ToColor(); /// /// Gets a color with an ARGB value of #fff0ffff. /// - public static Color Azure => Color.FromUInt32(0xfff0ffff); + public static Color Azure => KnownColor.Azure.ToColor(); /// /// Gets a color with an ARGB value of #fff5f5dc. /// - public static Color Beige => Color.FromUInt32(0xfff5f5dc); + public static Color Beige => KnownColor.Beige.ToColor(); /// /// Gets a color with an ARGB value of #ffffe4c4. /// - public static Color Bisque => Color.FromUInt32(0xffffe4c4); + public static Color Bisque => KnownColor.Bisque.ToColor(); /// /// Gets a color with an ARGB value of #ff000000. /// - public static Color Black => Color.FromUInt32(0xff000000); + public static Color Black => KnownColor.Black.ToColor(); /// /// Gets a color with an ARGB value of #ffffebcd. /// - public static Color BlanchedAlmond => Color.FromUInt32(0xffffebcd); + public static Color BlanchedAlmond => KnownColor.BlanchedAlmond.ToColor(); /// /// Gets a color with an ARGB value of #ff0000ff. /// - public static Color Blue => Color.FromUInt32(0xff0000ff); + public static Color Blue => KnownColor.Blue.ToColor(); /// /// Gets a color with an ARGB value of #ff8a2be2. /// - public static Color BlueViolet => Color.FromUInt32(0xff8a2be2); + public static Color BlueViolet => KnownColor.BlueViolet.ToColor(); /// /// Gets a color with an ARGB value of #ffa52a2a. /// - public static Color Brown => Color.FromUInt32(0xffa52a2a); + public static Color Brown => KnownColor.Brown.ToColor(); /// /// Gets a color with an ARGB value of #ffdeb887. /// - public static Color BurlyWood => Color.FromUInt32(0xffdeb887); + public static Color BurlyWood => KnownColor.BurlyWood.ToColor(); /// /// Gets a color with an ARGB value of #ff5f9ea0. /// - public static Color CadetBlue => Color.FromUInt32(0xff5f9ea0); + public static Color CadetBlue => KnownColor.CadetBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff7fff00. /// - public static Color Chartreuse => Color.FromUInt32(0xff7fff00); + public static Color Chartreuse => KnownColor.Chartreuse.ToColor(); /// /// Gets a color with an ARGB value of #ffd2691e. /// - public static Color Chocolate => Color.FromUInt32(0xffd2691e); + public static Color Chocolate => KnownColor.Chocolate.ToColor(); /// /// Gets a color with an ARGB value of #ffff7f50. /// - public static Color Coral => Color.FromUInt32(0xffff7f50); + public static Color Coral => KnownColor.Coral.ToColor(); /// /// Gets a color with an ARGB value of #ff6495ed. /// - public static Color CornflowerBlue => Color.FromUInt32(0xff6495ed); + public static Color CornflowerBlue => KnownColor.CornflowerBlue.ToColor(); /// /// Gets a color with an ARGB value of #fffff8dc. /// - public static Color Cornsilk => Color.FromUInt32(0xfffff8dc); + public static Color Cornsilk => KnownColor.Cornsilk.ToColor(); /// /// Gets a color with an ARGB value of #ffdc143c. /// - public static Color Crimson => Color.FromUInt32(0xffdc143c); + public static Color Crimson => KnownColor.Crimson.ToColor(); /// /// Gets a color with an ARGB value of #ff00ffff. /// - public static Color Cyan => Color.FromUInt32(0xff00ffff); + public static Color Cyan => KnownColor.Cyan.ToColor(); /// /// Gets a color with an ARGB value of #ff00008b. /// - public static Color DarkBlue => Color.FromUInt32(0xff00008b); + public static Color DarkBlue => KnownColor.DarkBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff008b8b. /// - public static Color DarkCyan => Color.FromUInt32(0xff008b8b); + public static Color DarkCyan => KnownColor.DarkCyan.ToColor(); /// /// Gets a color with an ARGB value of #ffb8860b. /// - public static Color DarkGoldenrod => Color.FromUInt32(0xffb8860b); + public static Color DarkGoldenrod => KnownColor.DarkGoldenrod.ToColor(); /// /// Gets a color with an ARGB value of #ffa9a9a9. /// - public static Color DarkGray => Color.FromUInt32(0xffa9a9a9); + public static Color DarkGray => KnownColor.DarkGray.ToColor(); /// /// Gets a color with an ARGB value of #ff006400. /// - public static Color DarkGreen => Color.FromUInt32(0xff006400); + public static Color DarkGreen => KnownColor.DarkGreen.ToColor(); /// /// Gets a color with an ARGB value of #ffbdb76b. /// - public static Color DarkKhaki => Color.FromUInt32(0xffbdb76b); + public static Color DarkKhaki => KnownColor.DarkKhaki.ToColor(); /// /// Gets a color with an ARGB value of #ff8b008b. /// - public static Color DarkMagenta => Color.FromUInt32(0xff8b008b); + public static Color DarkMagenta => KnownColor.DarkMagenta.ToColor(); /// /// Gets a color with an ARGB value of #ff556b2f. /// - public static Color DarkOliveGreen => Color.FromUInt32(0xff556b2f); + public static Color DarkOliveGreen => KnownColor.DarkOliveGreen.ToColor(); /// /// Gets a color with an ARGB value of #ffff8c00. /// - public static Color DarkOrange => Color.FromUInt32(0xffff8c00); + public static Color DarkOrange => KnownColor.DarkOrange.ToColor(); /// /// Gets a color with an ARGB value of #ff9932cc. /// - public static Color DarkOrchid => Color.FromUInt32(0xff9932cc); + public static Color DarkOrchid => KnownColor.DarkOrchid.ToColor(); /// /// Gets a color with an ARGB value of #ff8b0000. /// - public static Color DarkRed => Color.FromUInt32(0xff8b0000); + public static Color DarkRed => KnownColor.DarkRed.ToColor(); /// /// Gets a color with an ARGB value of #ffe9967a. /// - public static Color DarkSalmon => Color.FromUInt32(0xffe9967a); + public static Color DarkSalmon => KnownColor.DarkSalmon.ToColor(); /// /// Gets a color with an ARGB value of #ff8fbc8f. /// - public static Color DarkSeaGreen => Color.FromUInt32(0xff8fbc8f); + public static Color DarkSeaGreen => KnownColor.DarkSeaGreen.ToColor(); /// /// Gets a color with an ARGB value of #ff483d8b. /// - public static Color DarkSlateBlue => Color.FromUInt32(0xff483d8b); + public static Color DarkSlateBlue => KnownColor.DarkSlateBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff2f4f4f. /// - public static Color DarkSlateGray => Color.FromUInt32(0xff2f4f4f); + public static Color DarkSlateGray => KnownColor.DarkSlateGray.ToColor(); /// /// Gets a color with an ARGB value of #ff00ced1. /// - public static Color DarkTurquoise => Color.FromUInt32(0xff00ced1); + public static Color DarkTurquoise => KnownColor.DarkTurquoise.ToColor(); /// /// Gets a color with an ARGB value of #ff9400d3. /// - public static Color DarkViolet => Color.FromUInt32(0xff9400d3); + public static Color DarkViolet => KnownColor.DarkViolet.ToColor(); /// /// Gets a color with an ARGB value of #ffff1493. /// - public static Color DeepPink => Color.FromUInt32(0xffff1493); + public static Color DeepPink => KnownColor.DeepPink.ToColor(); /// /// Gets a color with an ARGB value of #ff00bfff. /// - public static Color DeepSkyBlue => Color.FromUInt32(0xff00bfff); + public static Color DeepSkyBlue => KnownColor.DeepSkyBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff696969. /// - public static Color DimGray => Color.FromUInt32(0xff696969); + public static Color DimGray => KnownColor.DimGray.ToColor(); /// /// Gets a color with an ARGB value of #ff1e90ff. /// - public static Color DodgerBlue => Color.FromUInt32(0xff1e90ff); + public static Color DodgerBlue => KnownColor.DodgerBlue.ToColor(); /// /// Gets a color with an ARGB value of #ffb22222. /// - public static Color Firebrick => Color.FromUInt32(0xffb22222); + public static Color Firebrick => KnownColor.Firebrick.ToColor(); /// /// Gets a color with an ARGB value of #fffffaf0. /// - public static Color FloralWhite => Color.FromUInt32(0xfffffaf0); + public static Color FloralWhite => KnownColor.FloralWhite.ToColor(); /// /// Gets a color with an ARGB value of #ff228b22. /// - public static Color ForestGreen => Color.FromUInt32(0xff228b22); + public static Color ForestGreen => KnownColor.ForestGreen.ToColor(); /// /// Gets a color with an ARGB value of #ffff00ff. /// - public static Color Fuchsia => Color.FromUInt32(0xffff00ff); + public static Color Fuchsia => KnownColor.Fuchsia.ToColor(); /// /// Gets a color with an ARGB value of #ffdcdcdc. /// - public static Color Gainsboro => Color.FromUInt32(0xffdcdcdc); + public static Color Gainsboro => KnownColor.Gainsboro.ToColor(); /// /// Gets a color with an ARGB value of #fff8f8ff. /// - public static Color GhostWhite => Color.FromUInt32(0xfff8f8ff); + public static Color GhostWhite => KnownColor.GhostWhite.ToColor(); /// /// Gets a color with an ARGB value of #ffffd700. /// - public static Color Gold => Color.FromUInt32(0xffffd700); + public static Color Gold => KnownColor.Gold.ToColor(); /// /// Gets a color with an ARGB value of #ffdaa520. /// - public static Color Goldenrod => Color.FromUInt32(0xffdaa520); + public static Color Goldenrod => KnownColor.Goldenrod.ToColor(); /// /// Gets a color with an ARGB value of #ff808080. /// - public static Color Gray => Color.FromUInt32(0xff808080); + public static Color Gray => KnownColor.Gray.ToColor(); /// /// Gets a color with an ARGB value of #ff008000. /// - public static Color Green => Color.FromUInt32(0xff008000); + public static Color Green => KnownColor.Green.ToColor(); /// /// Gets a color with an ARGB value of #ffadff2f. /// - public static Color GreenYellow => Color.FromUInt32(0xffadff2f); + public static Color GreenYellow => KnownColor.GreenYellow.ToColor(); /// /// Gets a color with an ARGB value of #fff0fff0. /// - public static Color Honeydew => Color.FromUInt32(0xfff0fff0); + public static Color Honeydew => KnownColor.Honeydew.ToColor(); /// /// Gets a color with an ARGB value of #ffff69b4. /// - public static Color HotPink => Color.FromUInt32(0xffff69b4); + public static Color HotPink => KnownColor.HotPink.ToColor(); /// /// Gets a color with an ARGB value of #ffcd5c5c. /// - public static Color IndianRed => Color.FromUInt32(0xffcd5c5c); + public static Color IndianRed => KnownColor.IndianRed.ToColor(); /// /// Gets a color with an ARGB value of #ff4b0082. /// - public static Color Indigo => Color.FromUInt32(0xff4b0082); + public static Color Indigo => KnownColor.Indigo.ToColor(); /// /// Gets a color with an ARGB value of #fffffff0. /// - public static Color Ivory => Color.FromUInt32(0xfffffff0); + public static Color Ivory => KnownColor.Ivory.ToColor(); /// /// Gets a color with an ARGB value of #fff0e68c. /// - public static Color Khaki => Color.FromUInt32(0xfff0e68c); + public static Color Khaki => KnownColor.Khaki.ToColor(); /// /// Gets a color with an ARGB value of #ffe6e6fa. /// - public static Color Lavender => Color.FromUInt32(0xffe6e6fa); + public static Color Lavender => KnownColor.Lavender.ToColor(); /// /// Gets a color with an ARGB value of #fffff0f5. /// - public static Color LavenderBlush => Color.FromUInt32(0xfffff0f5); + public static Color LavenderBlush => KnownColor.LavenderBlush.ToColor(); /// /// Gets a color with an ARGB value of #ff7cfc00. /// - public static Color LawnGreen => Color.FromUInt32(0xff7cfc00); + public static Color LawnGreen => KnownColor.LawnGreen.ToColor(); /// /// Gets a color with an ARGB value of #fffffacd. /// - public static Color LemonChiffon => Color.FromUInt32(0xfffffacd); + public static Color LemonChiffon => KnownColor.LemonChiffon.ToColor(); /// /// Gets a color with an ARGB value of #ffadd8e6. /// - public static Color LightBlue => Color.FromUInt32(0xffadd8e6); + public static Color LightBlue => KnownColor.LightBlue.ToColor(); /// /// Gets a color with an ARGB value of #fff08080. /// - public static Color LightCoral => Color.FromUInt32(0xfff08080); + public static Color LightCoral => KnownColor.LightCoral.ToColor(); /// /// Gets a color with an ARGB value of #ffe0ffff. /// - public static Color LightCyan => Color.FromUInt32(0xffe0ffff); + public static Color LightCyan => KnownColor.LightCyan.ToColor(); /// /// Gets a color with an ARGB value of #fffafad2. /// - public static Color LightGoldenrodYellow => Color.FromUInt32(0xfffafad2); + public static Color LightGoldenrodYellow => KnownColor.LightGoldenrodYellow.ToColor(); /// /// Gets a color with an ARGB value of #ffd3d3d3. /// - public static Color LightGray => Color.FromUInt32(0xffd3d3d3); + public static Color LightGray => KnownColor.LightGray.ToColor(); /// /// Gets a color with an ARGB value of #ff90ee90. /// - public static Color LightGreen => Color.FromUInt32(0xff90ee90); + public static Color LightGreen => KnownColor.LightGreen.ToColor(); /// /// Gets a color with an ARGB value of #ffffb6c1. /// - public static Color LightPink => Color.FromUInt32(0xffffb6c1); + public static Color LightPink => KnownColor.LightPink.ToColor(); /// /// Gets a color with an ARGB value of #ffffa07a. /// - public static Color LightSalmon => Color.FromUInt32(0xffffa07a); + public static Color LightSalmon => KnownColor.LightSalmon.ToColor(); /// /// Gets a color with an ARGB value of #ff20b2aa. /// - public static Color LightSeaGreen => Color.FromUInt32(0xff20b2aa); + public static Color LightSeaGreen => KnownColor.LightSeaGreen.ToColor(); /// /// Gets a color with an ARGB value of #ff87cefa. /// - public static Color LightSkyBlue => Color.FromUInt32(0xff87cefa); + public static Color LightSkyBlue => KnownColor.LightSkyBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff778899. /// - public static Color LightSlateGray => Color.FromUInt32(0xff778899); + public static Color LightSlateGray => KnownColor.LightSlateGray.ToColor(); /// /// Gets a color with an ARGB value of #ffb0c4de. /// - public static Color LightSteelBlue => Color.FromUInt32(0xffb0c4de); + public static Color LightSteelBlue => KnownColor.LightSteelBlue.ToColor(); /// /// Gets a color with an ARGB value of #ffffffe0. /// - public static Color LightYellow => Color.FromUInt32(0xffffffe0); + public static Color LightYellow => KnownColor.LightYellow.ToColor(); /// /// Gets a color with an ARGB value of #ff00ff00. /// - public static Color Lime => Color.FromUInt32(0xff00ff00); + public static Color Lime => KnownColor.Lime.ToColor(); /// /// Gets a color with an ARGB value of #ff32cd32. /// - public static Color LimeGreen => Color.FromUInt32(0xff32cd32); + public static Color LimeGreen => KnownColor.LimeGreen.ToColor(); /// /// Gets a color with an ARGB value of #fffaf0e6. /// - public static Color Linen => Color.FromUInt32(0xfffaf0e6); + public static Color Linen => KnownColor.Linen.ToColor(); /// /// Gets a color with an ARGB value of #ffff00ff. /// - public static Color Magenta => Color.FromUInt32(0xffff00ff); + public static Color Magenta => KnownColor.Magenta.ToColor(); /// /// Gets a color with an ARGB value of #ff800000. /// - public static Color Maroon => Color.FromUInt32(0xff800000); + public static Color Maroon => KnownColor.Maroon.ToColor(); /// /// Gets a color with an ARGB value of #ff66cdaa. /// - public static Color MediumAquamarine => Color.FromUInt32(0xff66cdaa); + public static Color MediumAquamarine => KnownColor.MediumAquamarine.ToColor(); /// /// Gets a color with an ARGB value of #ff0000cd. /// - public static Color MediumBlue => Color.FromUInt32(0xff0000cd); + public static Color MediumBlue => KnownColor.MediumBlue.ToColor(); /// /// Gets a color with an ARGB value of #ffba55d3. /// - public static Color MediumOrchid => Color.FromUInt32(0xffba55d3); + public static Color MediumOrchid => KnownColor.MediumOrchid.ToColor(); /// /// Gets a color with an ARGB value of #ff9370db. /// - public static Color MediumPurple => Color.FromUInt32(0xff9370db); + public static Color MediumPurple => KnownColor.MediumPurple.ToColor(); /// /// Gets a color with an ARGB value of #ff3cb371. /// - public static Color MediumSeaGreen => Color.FromUInt32(0xff3cb371); + public static Color MediumSeaGreen => KnownColor.MediumSeaGreen.ToColor(); /// /// Gets a color with an ARGB value of #ff7b68ee. /// - public static Color MediumSlateBlue => Color.FromUInt32(0xff7b68ee); + public static Color MediumSlateBlue => KnownColor.MediumSlateBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff00fa9a. /// - public static Color MediumSpringGreen => Color.FromUInt32(0xff00fa9a); + public static Color MediumSpringGreen => KnownColor.MediumSpringGreen.ToColor(); /// /// Gets a color with an ARGB value of #ff48d1cc. /// - public static Color MediumTurquoise => Color.FromUInt32(0xff48d1cc); + public static Color MediumTurquoise => KnownColor.MediumTurquoise.ToColor(); /// /// Gets a color with an ARGB value of #ffc71585. /// - public static Color MediumVioletRed => Color.FromUInt32(0xffc71585); + public static Color MediumVioletRed => KnownColor.MediumVioletRed.ToColor(); /// /// Gets a color with an ARGB value of #ff191970. /// - public static Color MidnightBlue => Color.FromUInt32(0xff191970); + public static Color MidnightBlue => KnownColor.MidnightBlue.ToColor(); /// /// Gets a color with an ARGB value of #fff5fffa. /// - public static Color MintCream => Color.FromUInt32(0xfff5fffa); + public static Color MintCream => KnownColor.MintCream.ToColor(); /// /// Gets a color with an ARGB value of #ffffe4e1. /// - public static Color MistyRose => Color.FromUInt32(0xffffe4e1); + public static Color MistyRose => KnownColor.MistyRose.ToColor(); /// /// Gets a color with an ARGB value of #ffffe4b5. /// - public static Color Moccasin => Color.FromUInt32(0xffffe4b5); + public static Color Moccasin => KnownColor.Moccasin.ToColor(); /// /// Gets a color with an ARGB value of #ffffdead. /// - public static Color NavajoWhite => Color.FromUInt32(0xffffdead); + public static Color NavajoWhite => KnownColor.NavajoWhite.ToColor(); /// /// Gets a color with an ARGB value of #ff000080. /// - public static Color Navy => Color.FromUInt32(0xff000080); + public static Color Navy => KnownColor.Navy.ToColor(); /// /// Gets a color with an ARGB value of #fffdf5e6. /// - public static Color OldLace => Color.FromUInt32(0xfffdf5e6); + public static Color OldLace => KnownColor.OldLace.ToColor(); /// /// Gets a color with an ARGB value of #ff808000. /// - public static Color Olive => Color.FromUInt32(0xff808000); + public static Color Olive => KnownColor.Olive.ToColor(); /// /// Gets a color with an ARGB value of #ff6b8e23. /// - public static Color OliveDrab => Color.FromUInt32(0xff6b8e23); + public static Color OliveDrab => KnownColor.OliveDrab.ToColor(); /// /// Gets a color with an ARGB value of #ffffa500. /// - public static Color Orange => Color.FromUInt32(0xffffa500); + public static Color Orange => KnownColor.Orange.ToColor(); /// /// Gets a color with an ARGB value of #ffff4500. /// - public static Color OrangeRed => Color.FromUInt32(0xffff4500); + public static Color OrangeRed => KnownColor.OrangeRed.ToColor(); /// /// Gets a color with an ARGB value of #ffda70d6. /// - public static Color Orchid => Color.FromUInt32(0xffda70d6); + public static Color Orchid => KnownColor.Orchid.ToColor(); /// /// Gets a color with an ARGB value of #ffeee8aa. /// - public static Color PaleGoldenrod => Color.FromUInt32(0xffeee8aa); + public static Color PaleGoldenrod => KnownColor.PaleGoldenrod.ToColor(); /// /// Gets a color with an ARGB value of #ff98fb98. /// - public static Color PaleGreen => Color.FromUInt32(0xff98fb98); + public static Color PaleGreen => KnownColor.PaleGreen.ToColor(); /// /// Gets a color with an ARGB value of #ffafeeee. /// - public static Color PaleTurquoise => Color.FromUInt32(0xffafeeee); + public static Color PaleTurquoise => KnownColor.PaleTurquoise.ToColor(); /// /// Gets a color with an ARGB value of #ffdb7093. /// - public static Color PaleVioletRed => Color.FromUInt32(0xffdb7093); + public static Color PaleVioletRed => KnownColor.PaleVioletRed.ToColor(); /// /// Gets a color with an ARGB value of #ffffefd5. /// - public static Color PapayaWhip => Color.FromUInt32(0xffffefd5); + public static Color PapayaWhip => KnownColor.PapayaWhip.ToColor(); /// /// Gets a color with an ARGB value of #ffffdab9. /// - public static Color PeachPuff => Color.FromUInt32(0xffffdab9); + public static Color PeachPuff => KnownColor.PeachPuff.ToColor(); /// /// Gets a color with an ARGB value of #ffcd853f. /// - public static Color Peru => Color.FromUInt32(0xffcd853f); + public static Color Peru => KnownColor.Peru.ToColor(); /// /// Gets a color with an ARGB value of #ffffc0cb. /// - public static Color Pink => Color.FromUInt32(0xffffc0cb); + public static Color Pink => KnownColor.Pink.ToColor(); /// /// Gets a color with an ARGB value of #ffdda0dd. /// - public static Color Plum => Color.FromUInt32(0xffdda0dd); + public static Color Plum => KnownColor.Plum.ToColor(); /// /// Gets a color with an ARGB value of #ffb0e0e6. /// - public static Color PowderBlue => Color.FromUInt32(0xffb0e0e6); + public static Color PowderBlue => KnownColor.PowderBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff800080. /// - public static Color Purple => Color.FromUInt32(0xff800080); + public static Color Purple => KnownColor.Purple.ToColor(); /// /// Gets a color with an ARGB value of #ffff0000. /// - public static Color Red => Color.FromUInt32(0xffff0000); + public static Color Red => KnownColor.Red.ToColor(); /// /// Gets a color with an ARGB value of #ffbc8f8f. /// - public static Color RosyBrown => Color.FromUInt32(0xffbc8f8f); + public static Color RosyBrown => KnownColor.RosyBrown.ToColor(); /// /// Gets a color with an ARGB value of #ff4169e1. /// - public static Color RoyalBlue => Color.FromUInt32(0xff4169e1); + public static Color RoyalBlue => KnownColor.RoyalBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff8b4513. /// - public static Color SaddleBrown => Color.FromUInt32(0xff8b4513); + public static Color SaddleBrown => KnownColor.SaddleBrown.ToColor(); /// /// Gets a color with an ARGB value of #fffa8072. /// - public static Color Salmon => Color.FromUInt32(0xfffa8072); + public static Color Salmon => KnownColor.Salmon.ToColor(); /// /// Gets a color with an ARGB value of #fff4a460. /// - public static Color SandyBrown => Color.FromUInt32(0xfff4a460); + public static Color SandyBrown => KnownColor.SandyBrown.ToColor(); /// /// Gets a color with an ARGB value of #ff2e8b57. /// - public static Color SeaGreen => Color.FromUInt32(0xff2e8b57); + public static Color SeaGreen => KnownColor.SeaGreen.ToColor(); /// /// Gets a color with an ARGB value of #fffff5ee. /// - public static Color SeaShell => Color.FromUInt32(0xfffff5ee); + public static Color SeaShell => KnownColor.SeaShell.ToColor(); /// /// Gets a color with an ARGB value of #ffa0522d. /// - public static Color Sienna => Color.FromUInt32(0xffa0522d); + public static Color Sienna => KnownColor.Sienna.ToColor(); /// /// Gets a color with an ARGB value of #ffc0c0c0. /// - public static Color Silver => Color.FromUInt32(0xffc0c0c0); + public static Color Silver => KnownColor.Silver.ToColor(); /// /// Gets a color with an ARGB value of #ff87ceeb. /// - public static Color SkyBlue => Color.FromUInt32(0xff87ceeb); + public static Color SkyBlue => KnownColor.SkyBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff6a5acd. /// - public static Color SlateBlue => Color.FromUInt32(0xff6a5acd); + public static Color SlateBlue => KnownColor.SlateBlue.ToColor(); /// /// Gets a color with an ARGB value of #ff708090. /// - public static Color SlateGray => Color.FromUInt32(0xff708090); + public static Color SlateGray => KnownColor.SlateGray.ToColor(); /// /// Gets a color with an ARGB value of #fffffafa. /// - public static Color Snow => Color.FromUInt32(0xfffffafa); + public static Color Snow => KnownColor.Snow.ToColor(); /// /// Gets a color with an ARGB value of #ff00ff7f. /// - public static Color SpringGreen => Color.FromUInt32(0xff00ff7f); + public static Color SpringGreen => KnownColor.SpringGreen.ToColor(); /// /// Gets a color with an ARGB value of #ff4682b4. /// - public static Color SteelBlue => Color.FromUInt32(0xff4682b4); + public static Color SteelBlue => KnownColor.SteelBlue.ToColor(); /// /// Gets a color with an ARGB value of #ffd2b48c. /// - public static Color Tan => Color.FromUInt32(0xffd2b48c); + public static Color Tan => KnownColor.Tan.ToColor(); /// /// Gets a color with an ARGB value of #ff008080. /// - public static Color Teal => Color.FromUInt32(0xff008080); + public static Color Teal => KnownColor.Teal.ToColor(); /// /// Gets a color with an ARGB value of #ffd8bfd8. /// - public static Color Thistle => Color.FromUInt32(0xffd8bfd8); + public static Color Thistle => KnownColor.Thistle.ToColor(); /// /// Gets a color with an ARGB value of #ffff6347. /// - public static Color Tomato => Color.FromUInt32(0xffff6347); + public static Color Tomato => KnownColor.Tomato.ToColor(); /// /// Gets a color with an ARGB value of #00ffffff. /// - public static Color Transparent => Color.FromUInt32(0x00ffffff); + public static Color Transparent => KnownColor.Transparent.ToColor(); /// /// Gets a color with an ARGB value of #ff40e0d0. /// - public static Color Turquoise => Color.FromUInt32(0xff40e0d0); + public static Color Turquoise => KnownColor.Turquoise.ToColor(); /// /// Gets a color with an ARGB value of #ffee82ee. /// - public static Color Violet => Color.FromUInt32(0xffee82ee); + public static Color Violet => KnownColor.Violet.ToColor(); /// /// Gets a color with an ARGB value of #fff5deb3. /// - public static Color Wheat => Color.FromUInt32(0xfff5deb3); + public static Color Wheat => KnownColor.Wheat.ToColor(); /// /// Gets a color with an ARGB value of #ffffffff. /// - public static Color White => Color.FromUInt32(0xffffffff); + public static Color White => KnownColor.White.ToColor(); /// /// Gets a color with an ARGB value of #fff5f5f5. /// - public static Color WhiteSmoke => Color.FromUInt32(0xfff5f5f5); + public static Color WhiteSmoke => KnownColor.WhiteSmoke.ToColor(); /// /// Gets a color with an ARGB value of #ffffff00. /// - public static Color Yellow => Color.FromUInt32(0xffffff00); + public static Color Yellow => KnownColor.Yellow.ToColor(); /// /// Gets a color with an ARGB value of #ff9acd32. /// - public static Color YellowGreen => Color.FromUInt32(0xff9acd32); + public static Color YellowGreen => KnownColor.YellowGreen.ToColor(); } } \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/KnownColors.cs b/src/Avalonia.Visuals/Media/KnownColors.cs new file mode 100644 index 0000000000..0887d2c913 --- /dev/null +++ b/src/Avalonia.Visuals/Media/KnownColors.cs @@ -0,0 +1,224 @@ +using System; +using System.Reflection; +using System.Collections.Generic; + +namespace Avalonia.Media +{ + internal static class KnownColors + { + private static readonly IReadOnlyDictionary _knownColorNames; + private static readonly IReadOnlyDictionary _knownColors; + private static readonly Dictionary _knownBrushes; + + static KnownColors() + { + var knownColorNames = new Dictionary(StringComparer.OrdinalIgnoreCase); + var knownColors = new Dictionary(); + + foreach (var field in typeof(KnownColor).GetRuntimeFields()) + { + if (field.FieldType != typeof(KnownColor)) continue; + var knownColor = (KnownColor)field.GetValue(null); + if (knownColor == KnownColor.None) continue; + + knownColorNames.Add(field.Name, knownColor); + + // some known colors have the same value, so use the first + if (!knownColors.ContainsKey((uint)knownColor)) + { + knownColors.Add((uint)knownColor, field.Name); + } + } + + _knownColorNames = knownColorNames; + _knownColors = knownColors; + _knownBrushes = new Dictionary(); + } + + public static ISolidColorBrush GetKnownBrush(string s) + { + var color = GetKnownColor(s); + return color != KnownColor.None ? color.ToBrush() : null; + } + + public static KnownColor GetKnownColor(string s) + { + if (_knownColorNames.TryGetValue(s, out var color)) + { + return color; + } + + return KnownColor.None; + } + + public static string GetKnownColorName(uint rgb) + { + return _knownColors.TryGetValue(rgb, out var name) ? name : null; + } + + public static Color ToColor(this KnownColor color) + { + return Color.FromUInt32((uint)color); + } + + public static ISolidColorBrush ToBrush(this KnownColor color) + { + lock (_knownBrushes) + { + if (!_knownBrushes.TryGetValue(color, out var brush)) + { + brush = new Immutable.ImmutableSolidColorBrush(color.ToColor()); + _knownBrushes.Add(color, brush); + } + + return brush; + } + } + } + + internal enum KnownColor : uint + { + None, + AliceBlue = 0xfff0f8ff, + AntiqueWhite = 0xfffaebd7, + Aqua = 0xff00ffff, + Aquamarine = 0xff7fffd4, + Azure = 0xfff0ffff, + Beige = 0xfff5f5dc, + Bisque = 0xffffe4c4, + Black = 0xff000000, + BlanchedAlmond = 0xffffebcd, + Blue = 0xff0000ff, + BlueViolet = 0xff8a2be2, + Brown = 0xffa52a2a, + BurlyWood = 0xffdeb887, + CadetBlue = 0xff5f9ea0, + Chartreuse = 0xff7fff00, + Chocolate = 0xffd2691e, + Coral = 0xffff7f50, + CornflowerBlue = 0xff6495ed, + Cornsilk = 0xfffff8dc, + Crimson = 0xffdc143c, + Cyan = 0xff00ffff, + DarkBlue = 0xff00008b, + DarkCyan = 0xff008b8b, + DarkGoldenrod = 0xffb8860b, + DarkGray = 0xffa9a9a9, + DarkGreen = 0xff006400, + DarkKhaki = 0xffbdb76b, + DarkMagenta = 0xff8b008b, + DarkOliveGreen = 0xff556b2f, + DarkOrange = 0xffff8c00, + DarkOrchid = 0xff9932cc, + DarkRed = 0xff8b0000, + DarkSalmon = 0xffe9967a, + DarkSeaGreen = 0xff8fbc8f, + DarkSlateBlue = 0xff483d8b, + DarkSlateGray = 0xff2f4f4f, + DarkTurquoise = 0xff00ced1, + DarkViolet = 0xff9400d3, + DeepPink = 0xffff1493, + DeepSkyBlue = 0xff00bfff, + DimGray = 0xff696969, + DodgerBlue = 0xff1e90ff, + Firebrick = 0xffb22222, + FloralWhite = 0xfffffaf0, + ForestGreen = 0xff228b22, + Fuchsia = 0xffff00ff, + Gainsboro = 0xffdcdcdc, + GhostWhite = 0xfff8f8ff, + Gold = 0xffffd700, + Goldenrod = 0xffdaa520, + Gray = 0xff808080, + Green = 0xff008000, + GreenYellow = 0xffadff2f, + Honeydew = 0xfff0fff0, + HotPink = 0xffff69b4, + IndianRed = 0xffcd5c5c, + Indigo = 0xff4b0082, + Ivory = 0xfffffff0, + Khaki = 0xfff0e68c, + Lavender = 0xffe6e6fa, + LavenderBlush = 0xfffff0f5, + LawnGreen = 0xff7cfc00, + LemonChiffon = 0xfffffacd, + LightBlue = 0xffadd8e6, + LightCoral = 0xfff08080, + LightCyan = 0xffe0ffff, + LightGoldenrodYellow = 0xfffafad2, + LightGreen = 0xff90ee90, + LightGray = 0xffd3d3d3, + LightPink = 0xffffb6c1, + LightSalmon = 0xffffa07a, + LightSeaGreen = 0xff20b2aa, + LightSkyBlue = 0xff87cefa, + LightSlateGray = 0xff778899, + LightSteelBlue = 0xffb0c4de, + LightYellow = 0xffffffe0, + Lime = 0xff00ff00, + LimeGreen = 0xff32cd32, + Linen = 0xfffaf0e6, + Magenta = 0xffff00ff, + Maroon = 0xff800000, + MediumAquamarine = 0xff66cdaa, + MediumBlue = 0xff0000cd, + MediumOrchid = 0xffba55d3, + MediumPurple = 0xff9370db, + MediumSeaGreen = 0xff3cb371, + MediumSlateBlue = 0xff7b68ee, + MediumSpringGreen = 0xff00fa9a, + MediumTurquoise = 0xff48d1cc, + MediumVioletRed = 0xffc71585, + MidnightBlue = 0xff191970, + MintCream = 0xfff5fffa, + MistyRose = 0xffffe4e1, + Moccasin = 0xffffe4b5, + NavajoWhite = 0xffffdead, + Navy = 0xff000080, + OldLace = 0xfffdf5e6, + Olive = 0xff808000, + OliveDrab = 0xff6b8e23, + Orange = 0xffffa500, + OrangeRed = 0xffff4500, + Orchid = 0xffda70d6, + PaleGoldenrod = 0xffeee8aa, + PaleGreen = 0xff98fb98, + PaleTurquoise = 0xffafeeee, + PaleVioletRed = 0xffdb7093, + PapayaWhip = 0xffffefd5, + PeachPuff = 0xffffdab9, + Peru = 0xffcd853f, + Pink = 0xffffc0cb, + Plum = 0xffdda0dd, + PowderBlue = 0xffb0e0e6, + Purple = 0xff800080, + Red = 0xffff0000, + RosyBrown = 0xffbc8f8f, + RoyalBlue = 0xff4169e1, + SaddleBrown = 0xff8b4513, + Salmon = 0xfffa8072, + SandyBrown = 0xfff4a460, + SeaGreen = 0xff2e8b57, + SeaShell = 0xfffff5ee, + Sienna = 0xffa0522d, + Silver = 0xffc0c0c0, + SkyBlue = 0xff87ceeb, + SlateBlue = 0xff6a5acd, + SlateGray = 0xff708090, + Snow = 0xfffffafa, + SpringGreen = 0xff00ff7f, + SteelBlue = 0xff4682b4, + Tan = 0xffd2b48c, + Teal = 0xff008080, + Thistle = 0xffd8bfd8, + Tomato = 0xffff6347, + Transparent = 0x00ffffff, + Turquoise = 0xff40e0d0, + Violet = 0xffee82ee, + Wheat = 0xfff5deb3, + White = 0xffffffff, + WhiteSmoke = 0xfff5f5f5, + Yellow = 0xffffff00, + YellowGreen = 0xff9acd32 + } +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 5fbd082967..d0c3edfeb2 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Utilities; using System; using System.Globalization; using System.Linq; @@ -173,17 +174,12 @@ namespace Avalonia /// The . public static Point Parse(string s, CultureInfo culture) { - var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .ToList(); - - if (parts.Count == 2) - { - return new Point(double.Parse(parts[0], culture), double.Parse(parts[1], culture)); - } - else + using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Point")) { - throw new FormatException("Invalid Point."); + return new Point( + tokenizer.ReadDouble(), + tokenizer.ReadDouble() + ); } } diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index d562429fc7..748928ada3 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Utilities; using System; using System.Globalization; using System.Linq; @@ -490,21 +491,14 @@ namespace Avalonia /// The parsed . public static Rect Parse(string s, CultureInfo culture) { - var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .ToList(); - - if (parts.Count == 4) + using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Rect")) { return new Rect( - double.Parse(parts[0], culture), - double.Parse(parts[1], culture), - double.Parse(parts[2], culture), - double.Parse(parts[3], culture)); - } - else - { - throw new FormatException("Invalid Rect."); + tokenizer.ReadDouble(), + tokenizer.ReadDouble(), + tokenizer.ReadDouble(), + tokenizer.ReadDouble() + ); } } } diff --git a/src/Avalonia.Visuals/RelativePoint.cs b/src/Avalonia.Visuals/RelativePoint.cs index cc34feb5f3..a2ef0e6725 100644 --- a/src/Avalonia.Visuals/RelativePoint.cs +++ b/src/Avalonia.Visuals/RelativePoint.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Utilities; using System; using System.Globalization; using System.Linq; @@ -157,37 +158,32 @@ namespace Avalonia /// The parsed . public static RelativePoint Parse(string s, CultureInfo culture) { - var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .ToList(); - - if (parts.Count == 2) + using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativePoint")) { + var x = tokenizer.ReadString(); + var y = tokenizer.ReadString(); + var unit = RelativeUnit.Absolute; var scale = 1.0; - if (parts[0].EndsWith("%")) + if (x.EndsWith("%")) { - if (!parts[1].EndsWith("%")) + if (!y.EndsWith("%")) { throw new FormatException("If one coordinate is relative, both must be."); } - parts[0] = parts[0].TrimEnd('%'); - parts[1] = parts[1].TrimEnd('%'); + x = x.TrimEnd('%'); + y = y.TrimEnd('%'); unit = RelativeUnit.Relative; scale = 0.01; } return new RelativePoint( - double.Parse(parts[0], culture) * scale, - double.Parse(parts[1], culture) * scale, + double.Parse(x, culture) * scale, + double.Parse(y, culture) * scale, unit); } - else - { - throw new FormatException("Invalid Point."); - } } } } diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index a11f080e94..c13c3282db 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Utilities; using System; using System.Globalization; using System.Linq; @@ -12,6 +13,8 @@ namespace Avalonia /// public struct RelativeRect : IEquatable { + private static readonly char[] PercentChar = { '%' }; + /// /// A rectangle that represents 100% of an area. /// @@ -159,7 +162,7 @@ namespace Avalonia Rect.Width * size.Width, Rect.Height * size.Height); } - + /// /// Parses a string. /// @@ -168,43 +171,43 @@ namespace Avalonia /// The parsed . public static RelativeRect Parse(string s, CultureInfo culture) { - var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .ToList(); - - if (parts.Count == 4) + using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativeRect")) { + var x = tokenizer.ReadString(); + var y = tokenizer.ReadString(); + var width = tokenizer.ReadString(); + var height = tokenizer.ReadString(); + var unit = RelativeUnit.Absolute; var scale = 1.0; - if (parts[0].EndsWith("%")) + var xRelative = x.EndsWith("%", StringComparison.Ordinal); + var yRelative = y.EndsWith("%", StringComparison.Ordinal); + var widthRelative = width.EndsWith("%", StringComparison.Ordinal); + var heightRelative = height.EndsWith("%", StringComparison.Ordinal); + + if (xRelative && yRelative && widthRelative && heightRelative) { - if (!parts[1].EndsWith("%") - || !parts[2].EndsWith("%") - || !parts[3].EndsWith("%")) - { - throw new FormatException("If one coordinate is relative, all other must be too."); - } - - parts[0] = parts[0].TrimEnd('%'); - parts[1] = parts[1].TrimEnd('%'); - parts[2] = parts[2].TrimEnd('%'); - parts[3] = parts[3].TrimEnd('%'); + x = x.TrimEnd(PercentChar); + y = y.TrimEnd(PercentChar); + width = width.TrimEnd(PercentChar); + height = height.TrimEnd(PercentChar); + unit = RelativeUnit.Relative; scale = 0.01; } + else if (xRelative || yRelative || widthRelative || heightRelative) + { + throw new FormatException("If one coordinate is relative, all must be."); + } return new RelativeRect( - double.Parse(parts[0], culture) * scale, - double.Parse(parts[1], culture) * scale, - double.Parse(parts[2], culture) * scale, - double.Parse(parts[3], culture) * scale, + double.Parse(x, culture) * scale, + double.Parse(y, culture) * scale, + double.Parse(width, culture) * scale, + double.Parse(height, culture) * scale, unit); } - else - { - throw new FormatException("Invalid RelativeRect."); - } } } } diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs index 5dff3715b3..9cf849f59b 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs @@ -41,12 +41,12 @@ namespace Avalonia.Rendering { add { + _tick += value; + if (_subscriberCount++ == 0) { Start(); } - - _tick += value; } remove diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index 6ad87c6120..c5eaa33b41 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Utilities; using System; using System.Globalization; using System.Linq; @@ -153,17 +154,11 @@ namespace Avalonia /// The . public static Size Parse(string s, CultureInfo culture) { - var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .ToList(); - - if (parts.Count == 2) - { - return new Size(double.Parse(parts[0], culture), double.Parse(parts[1], culture)); - } - else + using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Size")) { - throw new FormatException("Invalid Size."); + return new Size( + tokenizer.ReadDouble(), + tokenizer.ReadDouble()); } } diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs index ead9fd004a..a5ca0a04a8 100644 --- a/src/Avalonia.Visuals/Thickness.cs +++ b/src/Avalonia.Visuals/Thickness.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Utilities; using System; using System.Globalization; using System.Linq; @@ -168,28 +169,22 @@ namespace Avalonia /// The . public static Thickness Parse(string s, CultureInfo culture) { - var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .ToList(); - - switch (parts.Count) + using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Thickness")) { - case 1: - var uniform = double.Parse(parts[0], culture); - return new Thickness(uniform); - case 2: - var horizontal = double.Parse(parts[0], culture); - var vertical = double.Parse(parts[1], culture); - return new Thickness(horizontal, vertical); - case 4: - var left = double.Parse(parts[0], culture); - var top = double.Parse(parts[1], culture); - var right = double.Parse(parts[2], culture); - var bottom = double.Parse(parts[3], culture); - return new Thickness(left, top, right, bottom); + var a = tokenizer.ReadDouble(); + + if (tokenizer.TryReadDouble(out var b)) + { + if (tokenizer.TryReadDouble(out var c)) + { + return new Thickness(a, b, c, tokenizer.ReadDouble()); + } + + return new Thickness(a, b); + } + + return new Thickness(a); } - - throw new FormatException("Invalid Thickness."); } /// diff --git a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs index ac547b8bc2..d6a3c1f260 100644 --- a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs +++ b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs @@ -32,7 +32,10 @@ namespace Avalonia.Gtk3 {StandardCursorType.TopLeftCorner, CursorType.TopLeftCorner}, {StandardCursorType.TopRightCorner, CursorType.TopRightCorner}, {StandardCursorType.BottomLeftCorner, CursorType.BottomLeftCorner}, - {StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner} + {StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner}, + {StandardCursorType.DragCopy, CursorType.CenterPtr}, + {StandardCursorType.DragMove, CursorType.Fleur}, + {StandardCursorType.DragLink, CursorType.Cross}, }; private static readonly Dictionary Cache = diff --git a/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj b/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj index 5f6be91571..c31c131ea9 100644 --- a/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj +++ b/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 True @@ -19,4 +19,4 @@ - + \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/Cursor.cs b/src/OSX/Avalonia.MonoMac/Cursor.cs index 10445e62e2..d9370e527b 100644 --- a/src/OSX/Avalonia.MonoMac/Cursor.cs +++ b/src/OSX/Avalonia.MonoMac/Cursor.cs @@ -51,6 +51,10 @@ namespace Avalonia.MonoMac [StandardCursorType.TopSide] = NSCursor.ResizeUpCursor, [StandardCursorType.UpArrow] = NSCursor.ResizeUpCursor, [StandardCursorType.Wait] = NSCursor.ArrowCursor, //TODO + [StandardCursorType.DragMove] = NSCursor.DragCopyCursor, // TODO + [StandardCursorType.DragCopy] = NSCursor.DragCopyCursor, + [StandardCursorType.DragLink] = NSCursor.DragLinkCursor, + }; } diff --git a/src/OSX/Avalonia.MonoMac/DragSource.cs b/src/OSX/Avalonia.MonoMac/DragSource.cs new file mode 100644 index 0000000000..41a206b580 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/DragSource.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Runtime.InteropServices; +using System.Runtime.Serialization.Formatters.Binary; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Input.Raw; +using MonoMac; +using MonoMac.AppKit; +using MonoMac.CoreGraphics; +using MonoMac.Foundation; +using MonoMac.OpenGL; + +namespace Avalonia.MonoMac +{ + public class DragSource : NSDraggingSource, IPlatformDragSource + { + private const string NSPasteboardTypeString = "public.utf8-plain-text"; + private const string NSPasteboardTypeFileUrl = "public.file-url"; + + private readonly Subject _result = new Subject(); + private readonly IInputManager _inputManager; + private DragDropEffects _allowedEffects; + + public override bool IgnoreModifierKeysWhileDragging => false; + + public DragSource() + { + _inputManager = AvaloniaLocator.Current.GetService(); + } + + private string DataFormatToUTI(string s) + { + if (s == DataFormats.FileNames) + return NSPasteboardTypeFileUrl; + if (s == DataFormats.Text) + return NSPasteboardTypeString; + return s; + } + + private NSDraggingItem CreateDraggingItem(string format, object data) + { + var pasteboardItem = new NSPasteboardItem(); + NSData nsData; + if (data is string s) + { + if (format == DataFormats.FileNames) + s = new Uri(s).AbsoluteUri; // Ensure file uris... + nsData = NSData.FromString(s); + } + else if (data is Stream strm) + nsData = NSData.FromStream(strm); + else if (data is byte[] bytes) + nsData = NSData.FromArray(bytes); + else + { + BinaryFormatter bf = new BinaryFormatter(); + using (var ms = new MemoryStream()) + { + bf.Serialize(ms, data); + ms.Position = 0; + nsData = NSData.FromStream(ms); + } + } + pasteboardItem.SetDataForType(nsData, DataFormatToUTI(format)); + + NSPasteboardWriting writing = new NSPasteboardWriting(pasteboardItem.Handle); + + return new NSDraggingItem(writing); + } + + public IEnumerable CreateDraggingItems(string format, object data) + { + if (format == DataFormats.FileNames && data is IEnumerable files) + { + foreach (var file in files) + yield return CreateDraggingItem(format, file); + + yield break; + } + + yield return CreateDraggingItem(format, data); + } + + + public async Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects) + { + // We need the TopLevelImpl + a mouse location so we just wait for the next event. + var mouseEv = await _inputManager.PreProcess.OfType().FirstAsync(); + var view = ((mouseEv.Root as TopLevel)?.PlatformImpl as TopLevelImpl)?.View; + if (view == null) + return DragDropEffects.None; + + // Prepare the source event: + var pt = view.TranslateLocalPoint(mouseEv.Position).ToMonoMacPoint(); + var ev = NSEvent.MouseEvent(NSEventType.LeftMouseDown, pt, 0, 0, 0, null, 0, 0, 0); + + _allowedEffects = allowedEffects; + var items = data.GetDataFormats().SelectMany(fmt => CreateDraggingItems(fmt, data.Get(fmt))).ToArray(); + view.BeginDraggingSession(items ,ev, this); + + return await _result; + } + + public override NSDragOperation DraggingSourceOperationMaskForLocal(bool flag) + { + return DraggingInfo.ConvertDragOperation(_allowedEffects); + } + + public override void DraggedImageEndedAtOperation(NSImage image, CGPoint screenPoint, NSDragOperation operation) + { + _result.OnNext(DraggingInfo.ConvertDragOperation(operation)); + _result.OnCompleted(); + } + } +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/DraggingInfo.cs b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs new file mode 100644 index 0000000000..fc5f52713e --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/DraggingInfo.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Input; +using MonoMac.AppKit; +using MonoMac.Foundation; + +namespace Avalonia.MonoMac +{ + class DraggingInfo : IDataObject + { + private readonly NSDraggingInfo _info; + + public DraggingInfo(NSDraggingInfo info) + { + _info = info; + } + + internal static NSDragOperation ConvertDragOperation(DragDropEffects d) + { + NSDragOperation result = NSDragOperation.None; + if (d.HasFlag(DragDropEffects.Copy)) + result |= NSDragOperation.Copy; + if (d.HasFlag(DragDropEffects.Link)) + result |= NSDragOperation.Link; + if (d.HasFlag(DragDropEffects.Move)) + result |= NSDragOperation.Move; + return result; + } + + internal static DragDropEffects ConvertDragOperation(NSDragOperation d) + { + DragDropEffects result = DragDropEffects.None; + if (d.HasFlag(NSDragOperation.Copy)) + result |= DragDropEffects.Copy; + if (d.HasFlag(NSDragOperation.Link)) + result |= DragDropEffects.Link; + if (d.HasFlag(NSDragOperation.Move)) + result |= DragDropEffects.Move; + return result; + } + + public Point Location => new Point(_info.DraggingLocation.X, _info.DraggingLocation.Y); + + public IEnumerable GetDataFormats() + { + return _info.DraggingPasteboard.Types.Select(NSTypeToWellknownType); + } + + private string NSTypeToWellknownType(string type) + { + if (type == NSPasteboard.NSStringType) + return DataFormats.Text; + if (type == NSPasteboard.NSFilenamesType) + return DataFormats.FileNames; + return type; + } + + public string GetText() + { + return _info.DraggingPasteboard.GetStringForType(NSPasteboard.NSStringType); + } + + public IEnumerable GetFileNames() + { + using(var fileNames = (NSArray)_info.DraggingPasteboard.GetPropertyListForType(NSPasteboard.NSFilenamesType)) + { + if (fileNames != null) + return NSArray.StringArrayFromHandle(fileNames.Handle); + } + + return Enumerable.Empty(); + } + + public bool Contains(string dataFormat) + { + return GetDataFormats().Any(f => f == dataFormat); + } + + public object Get(string dataFormat) + { + if (dataFormat == DataFormats.Text) + return GetText(); + if (dataFormat == DataFormats.FileNames) + return GetFileNames(); + + return _info.DraggingPasteboard.GetDataForType(dataFormat).ToArray(); + } + } +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs index 5907459459..ba45ad8403 100644 --- a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -35,7 +35,8 @@ namespace Avalonia.MonoMac .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(s_renderLoop) - .Bind().ToConstant(PlatformThreadingInterface.Instance); + .Bind().ToConstant(PlatformThreadingInterface.Instance) + /*.Bind().ToTransient()*/; } public static void Initialize() diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index a655bc1ec5..db7f29f05b 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -18,6 +18,7 @@ namespace Avalonia.MonoMac { public TopLevelView View { get; } private readonly IMouseDevice _mouse = AvaloniaLocator.Current.GetService(); + private readonly IDragDropDevice _dragDevice = AvaloniaLocator.Current.GetService(); protected TopLevelImpl() { View = new TopLevelView(this); @@ -53,6 +54,10 @@ namespace Avalonia.MonoMac _tl = tl; _mouse = AvaloniaLocator.Current.GetService(); _keyboard = AvaloniaLocator.Current.GetService(); + + RegisterForDraggedTypes(new string[] { + "public.data" // register for any kind of data. + }); } protected override void Dispose(bool disposing) @@ -149,6 +154,48 @@ namespace Avalonia.MonoMac UpdateCursor(); } + private NSDragOperation SendRawDragEvent(NSDraggingInfo sender, RawDragEventType type) + { + Action input = _tl.Input; + IDragDropDevice dragDevice = _tl._dragDevice; + IInputRoot root = _tl?.InputRoot; + if (root == null || dragDevice == null || input == null) + return NSDragOperation.None; + + var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask); + DraggingInfo info = new DraggingInfo(sender); + var pt = TranslateLocalPoint(info.Location); + var args = new RawDragEvent(dragDevice, type, root, pt, info, dragOp); + input(args); + return DraggingInfo.ConvertDragOperation(args.Effects); + } + + public override NSDragOperation DraggingEntered(NSDraggingInfo sender) + { + return SendRawDragEvent(sender, RawDragEventType.DragEnter); + } + + public override NSDragOperation DraggingUpdated(NSDraggingInfo sender) + { + return SendRawDragEvent(sender, RawDragEventType.DragOver); + } + + public override void DraggingExited(NSDraggingInfo sender) + { + SendRawDragEvent(sender, RawDragEventType.DragLeave); + } + + public override bool PrepareForDragOperation(NSDraggingInfo sender) + { + return SendRawDragEvent(sender, RawDragEventType.DragOver) != NSDragOperation.None; + } + + public override bool PerformDragOperation(NSDraggingInfo sender) + { + return SendRawDragEvent(sender, RawDragEventType.Drop) != NSDragOperation.None; + } + + public override void SetFrameSize(CGSize newSize) { lock (SyncRoot) diff --git a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs index c76a5b5da5..124e33c5a3 100644 --- a/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs +++ b/src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs @@ -103,7 +103,7 @@ namespace Avalonia.Direct2D1 /// Converts a pen to a Direct2D stroke style. /// /// The pen to convert. - /// The render target. + /// The render target. /// The Direct2D brush. public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, SharpDX.Direct2D1.RenderTarget renderTarget) { @@ -114,7 +114,7 @@ namespace Avalonia.Direct2D1 /// Converts a pen to a Direct2D stroke style. /// /// The pen to convert. - /// The render target. + /// The factory associated with this resource. /// The Direct2D brush. public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, Factory factory) { @@ -127,13 +127,16 @@ namespace Avalonia.Direct2D1 EndCap = pen.EndLineCap.ToDirect2D(), DashCap = pen.DashCap.ToDirect2D() }; - var dashes = new float[0]; + float[] dashes = null; if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0) { properties.DashStyle = DashStyle.Custom; properties.DashOffset = (float)pen.DashStyle.Offset; - dashes = pen.DashStyle?.Dashes.Select(x => (float)x).ToArray(); + dashes = pen.DashStyle.Dashes.Select(x => (float)x).ToArray(); } + + dashes = dashes ?? Array.Empty(); + return new StrokeStyle(factory, properties, dashes); } diff --git a/src/Windows/Avalonia.Win32/ClipboardFormats.cs b/src/Windows/Avalonia.Win32/ClipboardFormats.cs new file mode 100644 index 0000000000..5e0bbab975 --- /dev/null +++ b/src/Windows/Avalonia.Win32/ClipboardFormats.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using Avalonia.Input; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + static class ClipboardFormats + { + private const int MAX_FORMAT_NAME_LENGTH = 260; + + class ClipboardFormat + { + public short Format { get; private set; } + public string Name { get; private set; } + public short[] Synthesized { get; private set; } + + public ClipboardFormat(string name, short format, params short[] synthesized) + { + Format = format; + Name = name; + Synthesized = synthesized; + } + } + + private static readonly List FormatList = new List() + { + new ClipboardFormat(DataFormats.Text, (short)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, (short)UnmanagedMethods.ClipboardFormat.CF_TEXT), + new ClipboardFormat(DataFormats.FileNames, (short)UnmanagedMethods.ClipboardFormat.CF_HDROP), + }; + + + private static string QueryFormatName(short format) + { + StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH); + if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0) + return sb.ToString(); + return null; + } + + public static string GetFormat(short format) + { + lock (FormatList) + { + var pd = FormatList.FirstOrDefault(f => f.Format == format || Array.IndexOf(f.Synthesized, format) >= 0); + if (pd == null) + { + string name = QueryFormatName(format); + if (string.IsNullOrEmpty(name)) + name = string.Format("Unknown_Format_{0}", format); + pd = new ClipboardFormat(name, format); + FormatList.Add(pd); + } + return pd.Name; + } + } + + public static short GetFormat(string format) + { + lock (FormatList) + { + var pd = FormatList.FirstOrDefault(f => StringComparer.OrdinalIgnoreCase.Equals(f.Name, format)); + if (pd == null) + { + int id = UnmanagedMethods.RegisterClipboardFormat(format); + if (id == 0) + throw new Win32Exception(); + pd = new ClipboardFormat(format, (short)id); + FormatList.Add(pd); + } + return pd.Format; + } + } + + + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index 0d529d6b91..fa2fbe4810 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Platform; +using System.Runtime.InteropServices; namespace Avalonia.Win32 { @@ -20,6 +21,27 @@ namespace Avalonia.Win32 { } + static CursorFactory() + { + LoadModuleCursor(StandardCursorType.DragMove, "ole32.dll", 2); + LoadModuleCursor(StandardCursorType.DragCopy, "ole32.dll", 3); + LoadModuleCursor(StandardCursorType.DragLink, "ole32.dll", 4); + } + + private static void LoadModuleCursor(StandardCursorType cursorType, string module, int id) + { + IntPtr mh = UnmanagedMethods.GetModuleHandle(module); + if (mh != IntPtr.Zero) + { + IntPtr cursor = UnmanagedMethods.LoadCursor(mh, new IntPtr(id)); + if (cursor != IntPtr.Zero) + { + PlatformHandle phCursor = new PlatformHandle(cursor, PlatformConstants.CursorHandleType); + Cache.Add(cursorType, phCursor); + } + } + } + private static readonly Dictionary CursorTypeMapping = new Dictionary { @@ -47,6 +69,11 @@ namespace Avalonia.Win32 //Using SizeNorthEastSouthWest {StandardCursorType.TopRightCorner, 32643}, {StandardCursorType.BottomLeftCorner, 32643}, + + // Fallback, should have been loaded from ole32.dll + {StandardCursorType.DragMove, 32516}, + {StandardCursorType.DragCopy, 32516}, + {StandardCursorType.DragLink, 32516}, }; private static readonly Dictionary Cache = diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs new file mode 100644 index 0000000000..34867765e5 --- /dev/null +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -0,0 +1,361 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using Avalonia.Input; +using Avalonia.Win32.Interop; +using IDataObject = Avalonia.Input.IDataObject; +using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; + +namespace Avalonia.Win32 +{ + class DataObject : IDataObject, IOleDataObject + { + // Compatibility with WinForms + WPF... + internal static readonly byte[] SerializedObjectGUID = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray(); + + class FormatEnumerator : IEnumFORMATETC + { + private FORMATETC[] _formats; + private int _current; + + private FormatEnumerator(FORMATETC[] formats, int current) + { + _formats = formats; + _current = current; + } + + public FormatEnumerator(IDataObject dataobj) + { + _formats = dataobj.GetDataFormats().Select(ConvertToFormatEtc).ToArray(); + _current = 0; + } + + private FORMATETC ConvertToFormatEtc(string aFormatName) + { + FORMATETC result = default(FORMATETC); + result.cfFormat = ClipboardFormats.GetFormat(aFormatName); + result.dwAspect = DVASPECT.DVASPECT_CONTENT; + result.ptd = IntPtr.Zero; + result.lindex = -1; + result.tymed = TYMED.TYMED_HGLOBAL; + return result; + } + + public void Clone(out IEnumFORMATETC newEnum) + { + newEnum = new FormatEnumerator(_formats, _current); + } + + public int Next(int celt, FORMATETC[] rgelt, int[] pceltFetched) + { + if (rgelt == null) + return unchecked((int)UnmanagedMethods.HRESULT.E_INVALIDARG); + + int i = 0; + while (i < celt && _current < _formats.Length) + { + rgelt[i] = _formats[_current]; + _current++; + i++; + } + if (pceltFetched != null) + pceltFetched[0] = i; + + if (i != celt) + return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + + public int Reset() + { + _current = 0; + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + + public int Skip(int celt) + { + _current += Math.Min(celt, int.MaxValue - _current); + if (_current >= _formats.Length) + return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + } + + private const int DV_E_TYMED = unchecked((int)0x80040069); + private const int DV_E_DVASPECT = unchecked((int)0x8004006B); + private const int DV_E_FORMATETC = unchecked((int)0x80040064); + private const int OLE_E_ADVISENOTSUPPORTED = unchecked((int)0x80040003); + private const int STG_E_MEDIUMFULL = unchecked((int)0x80030070); + private const int GMEM_ZEROINIT = 0x0040; + private const int GMEM_MOVEABLE = 0x0002; + + + IDataObject _wrapped; + + public DataObject(IDataObject wrapped) + { + _wrapped = wrapped; + } + + #region IDataObject + bool IDataObject.Contains(string dataFormat) + { + return _wrapped.Contains(dataFormat); + } + + IEnumerable IDataObject.GetDataFormats() + { + return _wrapped.GetDataFormats(); + } + + IEnumerable IDataObject.GetFileNames() + { + return _wrapped.GetFileNames(); + } + + string IDataObject.GetText() + { + return _wrapped.GetText(); + } + + object IDataObject.Get(string dataFormat) + { + return _wrapped.Get(dataFormat); + } + #endregion + + #region IOleDataObject + + int IOleDataObject.DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) + { + if (_wrapped is IOleDataObject ole) + return ole.DAdvise(ref pFormatetc, advf, adviseSink, out connection); + connection = 0; + return OLE_E_ADVISENOTSUPPORTED; + } + + void IOleDataObject.DUnadvise(int connection) + { + if (_wrapped is IOleDataObject ole) + ole.DUnadvise(connection); + Marshal.ThrowExceptionForHR(OLE_E_ADVISENOTSUPPORTED); + } + + int IOleDataObject.EnumDAdvise(out IEnumSTATDATA enumAdvise) + { + if (_wrapped is IOleDataObject ole) + return ole.EnumDAdvise(out enumAdvise); + + enumAdvise = null; + return OLE_E_ADVISENOTSUPPORTED; + } + + IEnumFORMATETC IOleDataObject.EnumFormatEtc(DATADIR direction) + { + if (_wrapped is IOleDataObject ole) + return ole.EnumFormatEtc(direction); + if (direction == DATADIR.DATADIR_GET) + return new FormatEnumerator(_wrapped); + throw new NotSupportedException(); + } + + int IOleDataObject.GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) + { + if (_wrapped is IOleDataObject ole) + return ole.GetCanonicalFormatEtc(ref formatIn, out formatOut); + + formatOut = new FORMATETC(); + formatOut.ptd = IntPtr.Zero; + return unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL); + } + + void IOleDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium) + { + if (_wrapped is IOleDataObject ole) + { + ole.GetData(ref format, out medium); + return; + } + if(!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL)) + Marshal.ThrowExceptionForHR(DV_E_TYMED); + + if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) + Marshal.ThrowExceptionForHR(DV_E_DVASPECT); + + string fmt = ClipboardFormats.GetFormat(format.cfFormat); + if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) + Marshal.ThrowExceptionForHR(DV_E_FORMATETC); + + medium = default(STGMEDIUM); + medium.tymed = TYMED.TYMED_HGLOBAL; + int result = WriteDataToHGlobal(fmt, ref medium.unionmember); + Marshal.ThrowExceptionForHR(result); + } + + void IOleDataObject.GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) + { + if (_wrapped is IOleDataObject ole) + { + ole.GetDataHere(ref format, ref medium); + return; + } + + if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasFlag(TYMED.TYMED_HGLOBAL)) + Marshal.ThrowExceptionForHR(DV_E_TYMED); + + if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) + Marshal.ThrowExceptionForHR(DV_E_DVASPECT); + + string fmt = ClipboardFormats.GetFormat(format.cfFormat); + if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) + Marshal.ThrowExceptionForHR(DV_E_FORMATETC); + + if (medium.unionmember == IntPtr.Zero) + Marshal.ThrowExceptionForHR(STG_E_MEDIUMFULL); + + int result = WriteDataToHGlobal(fmt, ref medium.unionmember); + Marshal.ThrowExceptionForHR(result); + } + + int IOleDataObject.QueryGetData(ref FORMATETC format) + { + if (_wrapped is IOleDataObject ole) + return ole.QueryGetData(ref format); + if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) + return DV_E_DVASPECT; + if (!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL)) + return DV_E_TYMED; + + string dataFormat = ClipboardFormats.GetFormat(format.cfFormat); + if (!string.IsNullOrEmpty(dataFormat) && _wrapped.Contains(dataFormat)) + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + return DV_E_FORMATETC; + } + + void IOleDataObject.SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) + { + if (_wrapped is IOleDataObject ole) + { + ole.SetData(ref formatIn, ref medium, release); + return; + } + Marshal.ThrowExceptionForHR(unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL)); + } + + private int WriteDataToHGlobal(string dataFormat, ref IntPtr hGlobal) + { + object data = _wrapped.Get(dataFormat); + if (dataFormat == DataFormats.Text || data is string) + return WriteStringToHGlobal(ref hGlobal, Convert.ToString(data)); + if (dataFormat == DataFormats.FileNames && data is IEnumerable files) + return WriteFileListToHGlobal(ref hGlobal, files); + if (data is Stream stream) + { + byte[] buffer = new byte[stream.Length - stream.Position]; + stream.Read(buffer, 0, buffer.Length); + return WriteBytesToHGlobal(ref hGlobal, buffer); + } + if (data is IEnumerable bytes) + { + var byteArr = bytes is byte[] ? (byte[])bytes : bytes.ToArray(); + return WriteBytesToHGlobal(ref hGlobal, byteArr); + } + return WriteBytesToHGlobal(ref hGlobal, SerializeObject(data)); + } + + private byte[] SerializeObject(object data) + { + using (var ms = new MemoryStream()) + { + ms.Write(SerializedObjectGUID, 0, SerializedObjectGUID.Length); + BinaryFormatter binaryFormatter = new BinaryFormatter(); + binaryFormatter.Serialize(ms, data); + return ms.ToArray(); + } + } + + private int WriteBytesToHGlobal(ref IntPtr hGlobal, byte[] data) + { + int required = data.Length; + if (hGlobal == IntPtr.Zero) + hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required); + + long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); + if (required > available) + return STG_E_MEDIUMFULL; + + IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); + try + { + Marshal.Copy(data, 0, ptr, data.Length); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } + } + + private int WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable files) + { + if (!files?.Any() ?? false) + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + + char[] filesStr = (string.Join("\0", files) + "\0\0").ToCharArray(); + _DROPFILES df = new _DROPFILES(); + df.pFiles = Marshal.SizeOf<_DROPFILES>(); + df.fWide = true; + + int required = (filesStr.Length * sizeof(char)) + Marshal.SizeOf<_DROPFILES>(); + if (hGlobal == IntPtr.Zero) + hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required); + + long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); + if (required > available) + return STG_E_MEDIUMFULL; + + IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); + try + { + Marshal.StructureToPtr(df, ptr, false); + + Marshal.Copy(filesStr, 0, ptr + Marshal.SizeOf<_DROPFILES>(), filesStr.Length); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } + } + + private int WriteStringToHGlobal(ref IntPtr hGlobal, string data) + { + int required = (data.Length + 1) * sizeof(char); + if (hGlobal == IntPtr.Zero) + hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, required); + + long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); + if (required > available) + return STG_E_MEDIUMFULL; + + IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); + try + { + char[] chars = (data + '\0').ToCharArray(); + Marshal.Copy(chars, 0, ptr, chars.Length); + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } + } + + #endregion + } +} diff --git a/src/Windows/Avalonia.Win32/DragSource.cs b/src/Windows/Avalonia.Win32/DragSource.cs new file mode 100644 index 0000000000..ea124e5f29 --- /dev/null +++ b/src/Windows/Avalonia.Win32/DragSource.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Threading; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class DragSource : IPlatformDragSource + { + public Task DoDragDrop(IDataObject data, DragDropEffects allowedEffects) + { + Dispatcher.UIThread.VerifyAccess(); + + OleDragSource src = new OleDragSource(); + DataObject dataObject = new DataObject(data); + int allowed = (int)OleDropTarget.ConvertDropEffect(allowedEffects); + + int[] finalEffect = new int[1]; + UnmanagedMethods.DoDragDrop(dataObject, src, allowed, finalEffect); + + return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect[0]));} + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f13dd3272c..aa86ab0f8d 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; using System.Text; // ReSharper disable InconsistentNaming @@ -951,6 +952,32 @@ namespace Avalonia.Win32.Interop [DllImport("msvcrt.dll", EntryPoint="memcpy", SetLastError = false, CallingConvention=CallingConvention.Cdecl)] public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count); + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IDropTarget target); + + [DllImport("ole32.dll", EntryPoint = "OleInitialize")] + public static extern HRESULT OleInitialize(IntPtr val); + + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + internal static extern void ReleaseStgMedium(ref STGMEDIUM medium); + + [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)] + public static extern int GetClipboardFormatName(int format, StringBuilder lpString, int cchMax); + + [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)] + public static extern int RegisterClipboardFormat(string format); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)] + public static extern IntPtr GlobalSize(IntPtr hGlobal); + + [DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)] + public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch); + + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] + public static extern void DoDragDrop(IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect); + + + public enum MONITOR { MONITOR_DEFAULTTONULL = 0x00000000, @@ -990,10 +1017,28 @@ namespace Avalonia.Win32.Interop MDT_DEFAULT = MDT_EFFECTIVE_DPI } - public enum ClipboardFormat + public enum ClipboardFormat { + /// + /// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. Use this format for ANSI text. + /// CF_TEXT = 1, - CF_UNICODETEXT = 13 + /// + /// A handle to a bitmap + /// + CF_BITMAP = 2, + /// + /// A memory object containing a BITMAPINFO structure followed by the bitmap bits. + /// + CF_DIB = 3, + /// + /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. + /// + CF_UNICODETEXT = 13, + /// + /// A handle to type HDROP that identifies a list of files. + /// + CF_HDROP = 15, } public struct MSG @@ -1136,7 +1181,9 @@ namespace Avalonia.Win32.Interop S_FALSE = 0x0001, S_OK = 0x0000, E_INVALIDARG = 0x80070057, - E_OUTOFMEMORY = 0x8007000E + E_OUTOFMEMORY = 0x8007000E, + E_NOTIMPL = 0x80004001, + E_UNEXPECTED = 0x8000FFFF, } public enum Icons @@ -1300,4 +1347,53 @@ namespace Avalonia.Win32.Interop uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); } + + [Flags] + internal enum DropEffect : int + { + None = 0, + Copy = 1, + Move = 2, + Link = 4, + Scroll = -2147483648, + } + + + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("00000122-0000-0000-C000-000000000046")] + internal interface IDropTarget + { + [PreserveSig] + UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + [PreserveSig] + UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + [PreserveSig] + UnmanagedMethods.HRESULT DragLeave(); + [PreserveSig] + UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + } + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("00000121-0000-0000-C000-000000000046")] + internal interface IDropSource + { + [PreserveSig] + int QueryContinueDrag(int fEscapePressed, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState); + [PreserveSig] + int GiveFeedback([MarshalAs(UnmanagedType.U4)] [In] int dwEffect); + } + + + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct _DROPFILES + { + public Int32 pFiles; + public Int32 X; + public Int32 Y; + public bool fNC; + public bool fWide; + } } diff --git a/src/Windows/Avalonia.Win32/OleContext.cs b/src/Windows/Avalonia.Win32/OleContext.cs new file mode 100644 index 0000000000..085c0f8ea9 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OleContext.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading; +using Avalonia.Platform; +using Avalonia.Threading; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class OleContext + { + private static OleContext fCurrent; + + internal static OleContext Current + { + get + { + if (!IsValidOleThread()) + return null; + + if (fCurrent == null) + fCurrent = new OleContext(); + return fCurrent; + } + } + + + private OleContext() + { + if (UnmanagedMethods.OleInitialize(IntPtr.Zero) != UnmanagedMethods.HRESULT.S_OK) + throw new SystemException("Failed to initialize OLE"); + } + + private static bool IsValidOleThread() + { + return Dispatcher.UIThread.CheckAccess() && + Thread.CurrentThread.GetApartmentState() == ApartmentState.STA; + } + + internal bool RegisterDragDrop(IPlatformHandle hwnd, IDropTarget target) + { + if (hwnd?.HandleDescriptor != "HWND" || target == null) + return false; + + return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, target) == UnmanagedMethods.HRESULT.S_OK; + } + } +} diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs new file mode 100644 index 0000000000..85d1daadeb --- /dev/null +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using Avalonia.Input; +using Avalonia.Win32.Interop; +using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; + +namespace Avalonia.Win32 +{ + class OleDataObject : Avalonia.Input.IDataObject + { + private IDataObject _wrapped; + + public OleDataObject(IDataObject wrapped) + { + _wrapped = wrapped; + } + + public bool Contains(string dataFormat) + { + return GetDataFormatsCore().Any(df => StringComparer.OrdinalIgnoreCase.Equals(df, dataFormat)); + } + + public IEnumerable GetDataFormats() + { + return GetDataFormatsCore().Distinct(); + } + + public string GetText() + { + return GetDataFromOleHGLOBAL(DataFormats.Text, DVASPECT.DVASPECT_CONTENT) as string; + } + + public IEnumerable GetFileNames() + { + return GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT) as IEnumerable; + } + + public object Get(string dataFormat) + { + return GetDataFromOleHGLOBAL(dataFormat, DVASPECT.DVASPECT_CONTENT); + } + + private object GetDataFromOleHGLOBAL(string format, DVASPECT aspect) + { + FORMATETC formatEtc = new FORMATETC(); + formatEtc.cfFormat = ClipboardFormats.GetFormat(format); + formatEtc.dwAspect = aspect; + formatEtc.lindex = -1; + formatEtc.tymed = TYMED.TYMED_HGLOBAL; + if (_wrapped.QueryGetData(ref formatEtc) == 0) + { + _wrapped.GetData(ref formatEtc, out STGMEDIUM medium); + try + { + if (medium.unionmember != IntPtr.Zero && medium.tymed == TYMED.TYMED_HGLOBAL) + { + if (format == DataFormats.Text) + return ReadStringFromHGlobal(medium.unionmember); + if (format == DataFormats.FileNames) + return ReadFileNamesFromHGlobal(medium.unionmember); + + byte[] data = ReadBytesFromHGlobal(medium.unionmember); + + if (IsSerializedObject(data)) + { + using (var ms = new MemoryStream(data)) + { + ms.Position = DataObject.SerializedObjectGUID.Length; + BinaryFormatter binaryFormatter = new BinaryFormatter(); + return binaryFormatter.Deserialize(ms); + } + } + return data; + } + } + finally + { + UnmanagedMethods.ReleaseStgMedium(ref medium); + } + } + return null; + } + + private bool IsSerializedObject(byte[] data) + { + if (data.Length < DataObject.SerializedObjectGUID.Length) + return false; + for (int i = 0; i < DataObject.SerializedObjectGUID.Length; i++) + if (data[i] != DataObject.SerializedObjectGUID[i]) + return false; + return true; + } + + private static IEnumerable ReadFileNamesFromHGlobal(IntPtr hGlobal) + { + List files = new List(); + int fileCount = UnmanagedMethods.DragQueryFile(hGlobal, -1, null, 0); + if (fileCount > 0) + { + for (int i = 0; i < fileCount; i++) + { + int pathLen = UnmanagedMethods.DragQueryFile(hGlobal, i, null, 0); + StringBuilder sb = new StringBuilder(pathLen+1); + + if (UnmanagedMethods.DragQueryFile(hGlobal, i, sb, sb.Capacity) == pathLen) + { + files.Add(sb.ToString()); + } + } + } + return files; + } + + private static string ReadStringFromHGlobal(IntPtr hGlobal) + { + IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); + try + { + return Marshal.PtrToStringAuto(ptr); + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } + } + + private static byte[] ReadBytesFromHGlobal(IntPtr hGlobal) + { + IntPtr source = UnmanagedMethods.GlobalLock(hGlobal); + try + { + int size = (int)UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); + byte[] data = new byte[size]; + Marshal.Copy(source, data, 0, size); + return data; + } + finally + { + UnmanagedMethods.GlobalUnlock(hGlobal); + } + } + + private IEnumerable GetDataFormatsCore() + { + var enumFormat = _wrapped.EnumFormatEtc(DATADIR.DATADIR_GET); + if (enumFormat != null) + { + enumFormat.Reset(); + FORMATETC[] formats = new FORMATETC[1]; + int[] fetched = { 1 }; + while (fetched[0] > 0) + { + fetched[0] = 0; + if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0) + { + if (formats[0].ptd != IntPtr.Zero) + Marshal.FreeCoTaskMem(formats[0].ptd); + + yield return ClipboardFormats.GetFormat(formats[0].cfFormat); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/OleDragSource.cs b/src/Windows/Avalonia.Win32/OleDragSource.cs new file mode 100644 index 0000000000..522014abc0 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OleDragSource.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class OleDragSource : IDropSource + { + private const int DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102; + private const int DRAGDROP_S_DROP = 0x00040100; + private const int DRAGDROP_S_CANCEL = 0x00040101; + + private const int KEYSTATE_LEFTMB = 1; + private const int KEYSTATE_MIDDLEMB = 16; + private const int KEYSTATE_RIGHTMB = 2; + private static readonly int[] MOUSE_BUTTONS = new int[] { KEYSTATE_LEFTMB, KEYSTATE_MIDDLEMB, KEYSTATE_RIGHTMB }; + + public int QueryContinueDrag(int fEscapePressed, int grfKeyState) + { + if (fEscapePressed != 0) + return DRAGDROP_S_CANCEL; + + int pressedMouseButtons = MOUSE_BUTTONS.Where(mb => (grfKeyState & mb) == mb).Count(); + + if (pressedMouseButtons >= 2) + return DRAGDROP_S_CANCEL; + if (pressedMouseButtons == 0) + return DRAGDROP_S_DROP; + + return unchecked((int)UnmanagedMethods.HRESULT.S_OK); + } + + public int GiveFeedback(int dwEffect) + { + return DRAGDROP_S_USEDEFAULTCURSORS; + } + } +} diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs new file mode 100644 index 0000000000..500c03e317 --- /dev/null +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -0,0 +1,160 @@ +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Platform; +using Avalonia.Win32.Interop; +using IDataObject = Avalonia.Input.IDataObject; +using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; + +namespace Avalonia.Win32 +{ + class OleDropTarget : IDropTarget + { + private readonly IInputElement _target; + private readonly ITopLevelImpl _tl; + private readonly IDragDropDevice _dragDevice; + + private IDataObject _currentDrag = null; + + public OleDropTarget(ITopLevelImpl tl, IInputElement target) + { + _dragDevice = AvaloniaLocator.Current.GetService(); + _tl = tl; + _target = target; + } + + public static DropEffect ConvertDropEffect(DragDropEffects operation) + { + DropEffect result = DropEffect.None; + if (operation.HasFlag(DragDropEffects.Copy)) + result |= DropEffect.Copy; + if (operation.HasFlag(DragDropEffects.Move)) + result |= DropEffect.Move; + if (operation.HasFlag(DragDropEffects.Link)) + result |= DropEffect.Link; + return result; + } + + public static DragDropEffects ConvertDropEffect(DropEffect effect) + { + DragDropEffects result = DragDropEffects.None; + if (effect.HasFlag(DropEffect.Copy)) + result |= DragDropEffects.Copy; + if (effect.HasFlag(DropEffect.Move)) + result |= DragDropEffects.Move; + if (effect.HasFlag(DropEffect.Link)) + result |= DragDropEffects.Link; + return result; + } + + UnmanagedMethods.HRESULT IDropTarget.DragEnter(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect) + { + var dispatch = _tl?.Input; + if (dispatch == null) + { + pdwEffect = DropEffect.None; + return UnmanagedMethods.HRESULT.S_OK; + } + _currentDrag = pDataObj as IDataObject; + if (_currentDrag == null) + _currentDrag = new OleDataObject(pDataObj); + var args = new RawDragEvent( + _dragDevice, + RawDragEventType.DragEnter, + _target, + GetDragLocation(pt), + _currentDrag, + ConvertDropEffect(pdwEffect) + ); + dispatch(args); + pdwEffect = ConvertDropEffect(args.Effects); + + return UnmanagedMethods.HRESULT.S_OK; + } + + UnmanagedMethods.HRESULT IDropTarget.DragOver(int grfKeyState, long pt, ref DropEffect pdwEffect) + { + var dispatch = _tl?.Input; + if (dispatch == null) + { + pdwEffect = DropEffect.None; + return UnmanagedMethods.HRESULT.S_OK; + } + + var args = new RawDragEvent( + _dragDevice, + RawDragEventType.DragOver, + _target, + GetDragLocation(pt), + _currentDrag, + ConvertDropEffect(pdwEffect) + ); + dispatch(args); + pdwEffect = ConvertDropEffect(args.Effects); + + return UnmanagedMethods.HRESULT.S_OK; + } + + UnmanagedMethods.HRESULT IDropTarget.DragLeave() + { + try + { + _tl?.Input(new RawDragEvent( + _dragDevice, + RawDragEventType.DragLeave, + _target, + default(Point), + null, + DragDropEffects.None + )); + return UnmanagedMethods.HRESULT.S_OK; + } + finally + { + _currentDrag = null; + } + } + + UnmanagedMethods.HRESULT IDropTarget.Drop(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect) + { + try + { + var dispatch = _tl?.Input; + if (dispatch == null) + { + pdwEffect = DropEffect.None; + return UnmanagedMethods.HRESULT.S_OK; + } + + _currentDrag = pDataObj as IDataObject; + if (_currentDrag == null) + _currentDrag= new OleDataObject(pDataObj); + + var args = new RawDragEvent( + _dragDevice, + RawDragEventType.Drop, + _target, + GetDragLocation(pt), + _currentDrag, + ConvertDropEffect(pdwEffect) + ); + dispatch(args); + pdwEffect = ConvertDropEffect(args.Effects); + + return UnmanagedMethods.HRESULT.S_OK; + } + finally + { + _currentDrag = null; + } + } + + private Point GetDragLocation(long dragPoint) + { + int x = (int)dragPoint; + int y = (int)(dragPoint >> 32); + + Point screenPt = new Point(x, y); + return _target.PointToClient(screenPt); + } + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 113b2811dc..e1df24151d 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -2,16 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; -using Avalonia.Controls; using Avalonia.Platform; -using Avalonia.Utilities; using static Avalonia.Win32.Interop.UnmanagedMethods; -#if NETSTANDARD -using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; -#endif - namespace Avalonia.Win32 { public class ScreenImpl : IScreenImpl diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 4e1ba618a8..95077f82a1 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -1,27 +1,23 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Input.Platform; using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading; +using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Platform; -using Avalonia.Win32.Input; -using Avalonia.Win32.Interop; -using Avalonia.Controls; using Avalonia.Rendering; using Avalonia.Threading; -using System.IO; -#if NETSTANDARD -using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; -#else -using System.ComponentModel; -#endif +using Avalonia.Win32.Input; +using Avalonia.Win32.Interop; namespace Avalonia { @@ -86,6 +82,9 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); + if (OleContext.Current != null) + AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); + UseDeferredRendering = deferredRendering; _uiThread = UnmanagedMethods.GetCurrentThreadId(); } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index a67362d59f..bb3c4cf6e6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -34,6 +34,7 @@ namespace Avalonia.Win32 private double _scaling = 1; private WindowState _showWindowState; private FramebufferManager _framebuffer; + private OleDropTarget _dropTarget; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; #endif @@ -310,6 +311,7 @@ namespace Avalonia.Win32 public void SetInputRoot(IInputRoot inputRoot) { _owner = inputRoot; + CreateDropTarget(); } public void SetTitle(string title) @@ -699,6 +701,13 @@ namespace Avalonia.Win32 } } + private void CreateDropTarget() + { + OleDropTarget odt = new OleDropTarget(this, _owner); + if (OleContext.Current?.RegisterDragDrop(Handle, odt) ?? false) + _dropTarget = odt; + } + private Point DipFromLParam(IntPtr lParam) { return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index f5c0c6ec15..f42e0daf2a 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -58,7 +58,7 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public async Task Should_Convert_Get_String_To_Double() { - var data = new Class1 { StringValue = "5.6" }; + var data = new Class1 { StringValue = $"{5.6}" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); var result = await target.Take(1); @@ -94,12 +94,12 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void Should_Convert_Set_String_To_Double() { - var data = new Class1 { StringValue = (5.6).ToString() }; + var data = new Class1 { StringValue = $"{5.6}" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); target.OnNext(6.7); - Assert.Equal((6.7).ToString(), data.StringValue); + Assert.Equal($"{6.7}", data.StringValue); GC.KeepAlive(data); } @@ -111,7 +111,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); var result = await target.Take(1); - Assert.Equal((5.6).ToString(), result); + Assert.Equal($"{5.6}", result); GC.KeepAlive(data); } @@ -122,7 +122,7 @@ namespace Avalonia.Markup.UnitTests.Data var data = new Class1 { DoubleValue = 5.6 }; var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); - target.OnNext("6.7"); + target.OnNext($"{6.7}"); Assert.Equal(6.7, data.DoubleValue); @@ -318,15 +318,15 @@ namespace Avalonia.Markup.UnitTests.Data target.Subscribe(x => result.Add(x)); target.OnNext(1.2); - target.OnNext("3.4"); + target.OnNext($"{3.4}"); target.OnNext("bar"); Assert.Equal( new[] { - new BindingNotification("5.6"), - new BindingNotification("1.2"), - new BindingNotification("3.4"), + new BindingNotification($"{5.6}"), + new BindingNotification($"{1.2}"), + new BindingNotification($"{3.4}"), new BindingNotification( new InvalidCastException("'bar' is not a valid number."), BindingErrorType.Error) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 9bee92cd7a..ac25b7ccbe 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -8,6 +8,7 @@ using Avalonia.Markup.Xaml.Data; using Avalonia.Markup.Xaml.Styling; using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Styling; using Avalonia.UnitTests; using Portable.Xaml; @@ -378,8 +379,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var control = AvaloniaXamlLoader.Parse(xaml); var bk = control.Background; - Assert.IsType(bk); - Assert.Equal(Colors.White, (bk as SolidColorBrush).Color); + Assert.IsType(bk); + Assert.Equal(Colors.White, (bk as ISolidColorBrush).Color); } [Fact] @@ -515,7 +516,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.NotNull(brush); - Assert.Equal(Colors.White, ((SolidColorBrush)brush).Color); + Assert.Equal(Colors.White, ((ISolidColorBrush)brush).Color); style.TryGetResource("Double", out var d); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/BrushTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/BrushTests.cs index ae88a94073..a6015c52e5 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/BrushTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/BrushTests.cs @@ -12,7 +12,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Fact] public void Parse_Parses_RGB_Hash_Brush() { - var result = (SolidColorBrush)Brush.Parse("#ff8844"); + var result = (ISolidColorBrush)Brush.Parse("#ff8844"); Assert.Equal(0xff, result.Color.R); Assert.Equal(0x88, result.Color.G); @@ -23,7 +23,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Fact] public void Parse_Parses_ARGB_Hash_Brush() { - var result = (SolidColorBrush)Brush.Parse("#40ff8844"); + var result = (ISolidColorBrush)Brush.Parse("#40ff8844"); Assert.Equal(0xff, result.Color.R); Assert.Equal(0x88, result.Color.G); @@ -34,7 +34,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Fact] public void Parse_Parses_Named_Brush_Lowercase() { - var result = (SolidColorBrush)Brush.Parse("red"); + var result = (ISolidColorBrush)Brush.Parse("red"); Assert.Equal(0xff, result.Color.R); Assert.Equal(0x00, result.Color.G); @@ -45,7 +45,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Fact] public void Parse_Parses_Named_Brush_Uppercase() { - var result = (SolidColorBrush)Brush.Parse("RED"); + var result = (ISolidColorBrush)Brush.Parse("RED"); Assert.Equal(0xff, result.Color.R); Assert.Equal(0x00, result.Color.G); @@ -53,6 +53,16 @@ namespace Avalonia.Visuals.UnitTests.Media Assert.Equal(0xff, result.Color.A); } + [Fact] + public void Parse_ToString_Named_Brush_Roundtrip() + { + const string expectedName = "Red"; + var brush = (ISolidColorBrush)Brush.Parse(expectedName); + var name = brush.ToString(); + + Assert.Equal(expectedName, name); + } + [Fact] public void Parse_Hex_Value_Doesnt_Accept_Too_Few_Chars() { diff --git a/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs b/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs index 8ba4f3b739..9f25dcd413 100644 --- a/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Globalization; using Xunit; @@ -25,5 +26,12 @@ namespace Avalonia.Visuals.UnitTests Assert.Equal(new RelativeRect(0.1, 0.2, 0.4, 0.7, RelativeUnit.Relative), result, Compare); } + + [Fact] + public void Parse_Should_Throw_Mixed_Values() + { + Assert.Throws(() => + RelativeRect.Parse("10%, 20%, 40, 70%", CultureInfo.InvariantCulture)); + } } }