From 33daa820eb99c2e4abc113a9225c4c6c5670f4b0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 1 Jun 2017 13:01:09 +0300 Subject: [PATCH 001/110] Initial implementation of MonoMac backend --- .../Avalonia.MonoMac/Avalonia.MonoMac.csproj | 11 + src/OSX/Avalonia.MonoMac/Cursor.cs | 61 +++ .../Avalonia.MonoMac/EmulatedFramebuffer.cs | 74 ++++ src/OSX/Avalonia.MonoMac/Helpers.cs | 33 ++ src/OSX/Avalonia.MonoMac/KeyTransform.cs | 266 +++++++++++++ src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs | 77 ++++ .../PlatformThreadingInterface.cs | 65 ++++ src/OSX/Avalonia.MonoMac/PopupImpl.cs | 19 + src/OSX/Avalonia.MonoMac/Stubs.cs | 51 +++ src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 368 ++++++++++++++++++ src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs | 168 ++++++++ src/OSX/Avalonia.MonoMac/WindowImpl.cs | 92 +++++ 12 files changed, 1285 insertions(+) create mode 100644 src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj create mode 100644 src/OSX/Avalonia.MonoMac/Cursor.cs create mode 100644 src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs create mode 100644 src/OSX/Avalonia.MonoMac/Helpers.cs create mode 100644 src/OSX/Avalonia.MonoMac/KeyTransform.cs create mode 100644 src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs create mode 100644 src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs create mode 100644 src/OSX/Avalonia.MonoMac/PopupImpl.cs create mode 100644 src/OSX/Avalonia.MonoMac/Stubs.cs create mode 100644 src/OSX/Avalonia.MonoMac/TopLevelImpl.cs create mode 100644 src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs create mode 100644 src/OSX/Avalonia.MonoMac/WindowImpl.cs diff --git a/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj b/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj new file mode 100644 index 0000000000..8ae7dc8470 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj @@ -0,0 +1,11 @@ + + + netcoreapp2.0;netstandard2.0;net461 + True + + + + + + + diff --git a/src/OSX/Avalonia.MonoMac/Cursor.cs b/src/OSX/Avalonia.MonoMac/Cursor.cs new file mode 100644 index 0000000000..8f5f2c2063 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/Cursor.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using Avalonia.Input; +using Avalonia.Platform; +using MonoMac.AppKit; + +namespace Avalonia.MonoMac +{ + class Cursor : IPlatformHandle + { + + public NSCursor Native { get; } + + public IntPtr Handle => Native.Handle; + + public string HandleDescriptor => "NSCursor"; + + public Cursor(NSCursor native) + { + Native = native; + } + } + + class CursorFactoryStub : IStandardCursorFactory + { + Dictionary _cache; + public CursorFactoryStub() + { + //TODO: Load diagonal cursors from webkit + //See https://stackoverflow.com/q/10733228 + _cache = new Dictionary() + { + [StandardCursorType.Arrow] = NSCursor.ArrowCursor, + [StandardCursorType.AppStarting] = NSCursor.ArrowCursor, //TODO + [StandardCursorType.BottomLeftCorner] = NSCursor.CrosshairCursor, //TODO + [StandardCursorType.BottomRightCorner]= NSCursor.CrosshairCursor, //TODO + [StandardCursorType.BottomSize] = NSCursor.ResizeDownCursor, + [StandardCursorType.Cross] = NSCursor.CrosshairCursor, + [StandardCursorType.Hand] = NSCursor.PointingHandCursor, + [StandardCursorType.Help] = NSCursor.ContextualMenuCursor, + [StandardCursorType.Ibeam] = NSCursor.IBeamCursor, + [StandardCursorType.LeftSide] = NSCursor.ResizeLeftCursor, + [StandardCursorType.No] = NSCursor.OperationNotAllowedCursor, + [StandardCursorType.RightSide] = NSCursor.ResizeRightCursor, + [StandardCursorType.SizeAll] = NSCursor.CrosshairCursor, //TODO + [StandardCursorType.SizeNorthSouth] = NSCursor.ResizeUpDownCursor, + [StandardCursorType.SizeWestEast] = NSCursor.ResizeLeftRightCursor, + [StandardCursorType.TopLeftCorner] = NSCursor.CrosshairCursor, //TODO + [StandardCursorType.TopRightCorner] = NSCursor.CrosshairCursor, //TODO + [StandardCursorType.TopSide] = NSCursor.ResizeUpCursor, + [StandardCursorType.UpArrow] = NSCursor.ResizeUpCursor, + [StandardCursorType.Wait] = NSCursor.ArrowCursor, //TODO + }; + } + + public IPlatformHandle GetCursor(StandardCursorType cursorType) + { + return new Cursor(_cache[cursorType]); + } + } +} diff --git a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs new file mode 100644 index 0000000000..11cc1e0151 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs @@ -0,0 +1,74 @@ +using System; +using Avalonia.Platform; +using MonoMac.AppKit; +using System.Runtime.InteropServices; +using MonoMac.CoreGraphics; +using MonoMac.ImageIO; +using MonoMac.Foundation; + +namespace Avalonia.MonoMac +{ + class EmulatedFramebuffer : ILockedFramebuffer + { + [DllImport("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/CoreGraphics.framework/CoreGraphics")] + extern static IntPtr CGBitmapContextCreate (IntPtr data, UIntPtr width, UIntPtr height, UIntPtr bitsPerComponent, + UIntPtr bytesPerRow, IntPtr colorSpace, uint bitmapInfo); + + public EmulatedFramebuffer(NSView view) + { + //TODO: Check if this is correct + var factor = view.Window.UserSpaceScaleFactor; + var frame = view.Frame; + Width = (int)(frame.Width * factor); + Height = (int)(frame.Height * factor); + RowBytes = Width * 4; + Dpi = new Size(96, 96) * factor; + Format = PixelFormat.Rgba8888; + Address = Marshal.AllocHGlobal(Height * RowBytes); + } + + public void Dispose() + { + if (Address == IntPtr.Zero) + return; + var nfo = (int)CGBitmapFlags.ByteOrder32Big | (int)CGImageAlphaInfo.PremultipliedLast; + + using (var colorSpace = CGColorSpace.CreateDeviceRGB()) + using (var bContext = new CGBitmapContext (Address, Width, Height, 8, Width * 4, + colorSpace, (CGImageAlphaInfo)nfo)) + using (var image = bContext.ToImage()) + using (var nscontext = NSGraphicsContext.CurrentContext) + using(var context = nscontext.GraphicsPort) + { + /* + var url = NSUrl.FromFilename("/tmp/wat.png"); + var dest = CGImageDestination.FromUrl(url, "public.png", 1); + dest.AddImage(image, new NSDictionary()); + dest.Close(); + dest.Dispose(); + + var buf = new byte[Height * RowBytes]; + Marshal.Copy(Address, buf, 0, buf.Length); + System.IO.File.WriteAllBytes("/tmp/wat.bin", buf); +*/ + // flip the image for CGContext.DrawImage + //context.TranslateCTM(0, Height); + //context.ScaleCTM(1, -1); + + context.SetFillColor(255, 255, 255, 255); + context.FillRect(new CGRect(0, 0, Width, Height)); + + context.DrawImage(new CGRect(0, 0, Width, Height), image); + } + Marshal.FreeHGlobal(Address); + Address = IntPtr.Zero; + } + + public IntPtr Address { get; private set; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + public Size Dpi { get; } + public PixelFormat Format { get; } + } +} diff --git a/src/OSX/Avalonia.MonoMac/Helpers.cs b/src/OSX/Avalonia.MonoMac/Helpers.cs new file mode 100644 index 0000000000..90fd88d725 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/Helpers.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.InteropServices; +using MonoMac.AppKit; +using MonoMac.ObjCRuntime; +using MonoMac.CoreGraphics; +using MonoMac; +namespace Avalonia.MonoMac +{ + static class Helpers + { + public static Point ToAvaloniaPoint(this CGPoint point) => new Point(point.X, point.Y); + public static CGPoint ToMonoMacPoint(this Point point) => new CGPoint(point.X, point.Y); + public static Size ToAvaloniaSize(this CGSize size) => new Size(size.Width, size.Height); + public static CGSize ToMonoMacSize(this Size size) => new CGSize(size.Width, size.Height); + public static Rect ToAvaloniaRect(this CGRect rect) => new Rect(rect.Left, rect.Top, rect.Width, rect.Height); + public static CGRect ToMonoMacRect(this Rect rect) => new CGRect(rect.X, rect.Y, rect.Width, rect.Height); + + public static Point ConvertPointY(this Point pt) + { + var sw = NSScreen.Screens[0].Frame; + var t = Math.Max(sw.Top, sw.Bottom); + return pt.WithY(t - pt.Y); + } + + public static CGPoint ConvertPointY(this CGPoint pt) + { + var sw = NSScreen.Screens[0].Frame; + var t = Math.Max(sw.Top, sw.Bottom); + return new CGPoint(pt.X, t - pt.Y); + } + + } +} diff --git a/src/OSX/Avalonia.MonoMac/KeyTransform.cs b/src/OSX/Avalonia.MonoMac/KeyTransform.cs new file mode 100644 index 0000000000..930b6f7b81 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/KeyTransform.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using Avalonia.Input; +using MonoMac.AppKit; + +namespace Avalonia.MonoMac +{ + public static class KeyTransform + { + // See /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h + private const int kVK_ANSI_A = 0x00; + private const int kVK_ANSI_S = 0x01; + private const int kVK_ANSI_D = 0x02; + private const int kVK_ANSI_F = 0x03; + private const int kVK_ANSI_H = 0x04; + private const int kVK_ANSI_G = 0x05; + private const int kVK_ANSI_Z = 0x06; + private const int kVK_ANSI_X = 0x07; + private const int kVK_ANSI_C = 0x08; + private const int kVK_ANSI_V = 0x09; + private const int kVK_ANSI_B = 0x0B; + private const int kVK_ANSI_Q = 0x0C; + private const int kVK_ANSI_W = 0x0D; + private const int kVK_ANSI_E = 0x0E; + private const int kVK_ANSI_R = 0x0F; + private const int kVK_ANSI_Y = 0x10; + private const int kVK_ANSI_T = 0x11; + private const int kVK_ANSI_1 = 0x12; + private const int kVK_ANSI_2 = 0x13; + private const int kVK_ANSI_3 = 0x14; + private const int kVK_ANSI_4 = 0x15; + private const int kVK_ANSI_6 = 0x16; + private const int kVK_ANSI_5 = 0x17; + private const int kVK_ANSI_Equal = 0x18; + private const int kVK_ANSI_9 = 0x19; + private const int kVK_ANSI_7 = 0x1A; + private const int kVK_ANSI_Minus = 0x1B; + private const int kVK_ANSI_8 = 0x1C; + private const int kVK_ANSI_0 = 0x1D; + private const int kVK_ANSI_RightBracket = 0x1E; + private const int kVK_ANSI_O = 0x1F; + private const int kVK_ANSI_U = 0x20; + private const int kVK_ANSI_LeftBracket = 0x21; + private const int kVK_ANSI_I = 0x22; + private const int kVK_ANSI_P = 0x23; + private const int kVK_ANSI_L = 0x25; + private const int kVK_ANSI_J = 0x26; + private const int kVK_ANSI_Quote = 0x27; + private const int kVK_ANSI_K = 0x28; + private const int kVK_ANSI_Semicolon = 0x29; + private const int kVK_ANSI_Backslash = 0x2A; + private const int kVK_ANSI_Comma = 0x2B; + private const int kVK_ANSI_Slash = 0x2C; + private const int kVK_ANSI_N = 0x2D; + private const int kVK_ANSI_M = 0x2E; + private const int kVK_ANSI_Period = 0x2F; + private const int kVK_ANSI_Grave = 0x32; + private const int kVK_ANSI_KeypadDecimal = 0x41; + private const int kVK_ANSI_KeypadMultiply = 0x43; + private const int kVK_ANSI_KeypadPlus = 0x45; + private const int kVK_ANSI_KeypadClear = 0x47; + private const int kVK_ANSI_KeypadDivide = 0x4B; + private const int kVK_ANSI_KeypadEnter = 0x4C; + private const int kVK_ANSI_KeypadMinus = 0x4E; + private const int kVK_ANSI_KeypadEquals = 0x51; + private const int kVK_ANSI_Keypad0 = 0x52; + private const int kVK_ANSI_Keypad1 = 0x53; + private const int kVK_ANSI_Keypad2 = 0x54; + private const int kVK_ANSI_Keypad3 = 0x55; + private const int kVK_ANSI_Keypad4 = 0x56; + private const int kVK_ANSI_Keypad5 = 0x57; + private const int kVK_ANSI_Keypad6 = 0x58; + private const int kVK_ANSI_Keypad7 = 0x59; + private const int kVK_ANSI_Keypad8 = 0x5B; + private const int kVK_ANSI_Keypad9 = 0x5C; + private const int kVK_Return = 0x24; + private const int kVK_Tab = 0x30; + private const int kVK_Space = 0x31; + private const int kVK_Delete = 0x33; + private const int kVK_Escape = 0x35; + private const int kVK_Command = 0x37; + private const int kVK_Shift = 0x38; + private const int kVK_CapsLock = 0x39; + private const int kVK_Option = 0x3A; + private const int kVK_Control = 0x3B; + private const int kVK_RightCommand = 0x36; + private const int kVK_RightShift = 0x3C; + private const int kVK_RightOption = 0x3D; + private const int kVK_RightControl = 0x3E; + private const int kVK_Function = 0x3F; + private const int kVK_F17 = 0x40; + private const int kVK_VolumeUp = 0x48; + private const int kVK_VolumeDown = 0x49; + private const int kVK_Mute = 0x4A; + private const int kVK_F18 = 0x4F; + private const int kVK_F19 = 0x50; + private const int kVK_F20 = 0x5A; + private const int kVK_F5 = 0x60; + private const int kVK_F6 = 0x61; + private const int kVK_F7 = 0x62; + private const int kVK_F3 = 0x63; + private const int kVK_F8 = 0x64; + private const int kVK_F9 = 0x65; + private const int kVK_F11 = 0x67; + private const int kVK_F13 = 0x69; + private const int kVK_F16 = 0x6A; + private const int kVK_F14 = 0x6B; + private const int kVK_F10 = 0x6D; + private const int kVK_F12 = 0x6F; + private const int kVK_F15 = 0x71; + private const int kVK_Help = 0x72; + private const int kVK_Home = 0x73; + private const int kVK_PageUp = 0x74; + private const int kVK_ForwardDelete = 0x75; + private const int kVK_F4 = 0x76; + private const int kVK_End = 0x77; + private const int kVK_F2 = 0x78; + private const int kVK_PageDown = 0x79; + private const int kVK_F1 = 0x7A; + private const int kVK_LeftArrow = 0x7B; + private const int kVK_RightArrow = 0x7C; + private const int kVK_DownArrow = 0x7D; + private const int kVK_UpArrow = 0x7E; + private const int kVK_ISO_Section = 0x0A; + private const int kVK_JIS_Yen = 0x5D; + private const int kVK_JIS_Underscore = 0x5E; + private const int kVK_JIS_KeypadComma = 0x5F; + private const int kVK_JIS_Eisu = 0x66; + private const int kVK_JIS_Kana = 0x68; + + //TODO: Map missing keys + static readonly Dictionary Keys = new Dictionary + { + [kVK_ANSI_A] = Key.A, + [kVK_ANSI_S] = Key.S, + [kVK_ANSI_D] = Key.D, + [kVK_ANSI_F] = Key.F, + [kVK_ANSI_H] = Key.H, + [kVK_ANSI_G] = Key.G, + [kVK_ANSI_Z] = Key.Z, + [kVK_ANSI_X] = Key.X, + [kVK_ANSI_C] = Key.C, + [kVK_ANSI_V] = Key.V, + [kVK_ANSI_B] = Key.B, + [kVK_ANSI_Q] = Key.Q, + [kVK_ANSI_W] = Key.W, + [kVK_ANSI_E] = Key.E, + [kVK_ANSI_R] = Key.R, + [kVK_ANSI_Y] = Key.Y, + [kVK_ANSI_T] = Key.T, + [kVK_ANSI_1] = Key.D1, + [kVK_ANSI_2] = Key.D2, + [kVK_ANSI_3] = Key.D3, + [kVK_ANSI_4] = Key.D4, + [kVK_ANSI_6] = Key.D6, + [kVK_ANSI_5] = Key.D5, + //[kVK_ANSI_Equal] = Key.?, + [kVK_ANSI_9] = Key.D9, + [kVK_ANSI_7] = Key.D7, + [kVK_ANSI_Minus] = Key.OemMinus, + [kVK_ANSI_8] = Key.D8, + [kVK_ANSI_0] = Key.D0, + [kVK_ANSI_RightBracket] = Key.OemCloseBrackets, + [kVK_ANSI_O] = Key.O, + [kVK_ANSI_U] = Key.U, + [kVK_ANSI_LeftBracket] = Key.OemOpenBrackets, + [kVK_ANSI_I] = Key.I, + [kVK_ANSI_P] = Key.P, + [kVK_ANSI_L] = Key.L, + [kVK_ANSI_J] = Key.J, + [kVK_ANSI_Quote] = Key.OemQuotes, + [kVK_ANSI_K] = Key.K, + [kVK_ANSI_Semicolon] = Key.OemSemicolon, + [kVK_ANSI_Backslash] = Key.OemBackslash, + [kVK_ANSI_Comma] = Key.OemComma, + //[kVK_ANSI_Slash] = Key.?, + [kVK_ANSI_N] = Key.N, + [kVK_ANSI_M] = Key.M, + [kVK_ANSI_Period] = Key.OemPeriod, + //[kVK_ANSI_Grave] = Key.?, + [kVK_ANSI_KeypadDecimal] = Key.Decimal, + [kVK_ANSI_KeypadMultiply] = Key.Multiply, + [kVK_ANSI_KeypadPlus] = Key.OemPlus, + [kVK_ANSI_KeypadClear] = Key.Clear, + [kVK_ANSI_KeypadDivide] = Key.Divide, + [kVK_ANSI_KeypadEnter] = Key.Enter, + [kVK_ANSI_KeypadMinus] = Key.OemMinus, + //[kVK_ANSI_KeypadEquals] = Key.?, + [kVK_ANSI_Keypad0] = Key.NumPad0, + [kVK_ANSI_Keypad1] = Key.NumPad1, + [kVK_ANSI_Keypad2] = Key.NumPad2, + [kVK_ANSI_Keypad3] = Key.NumPad3, + [kVK_ANSI_Keypad4] = Key.NumPad4, + [kVK_ANSI_Keypad5] = Key.NumPad5, + [kVK_ANSI_Keypad6] = Key.NumPad6, + [kVK_ANSI_Keypad7] = Key.NumPad7, + [kVK_ANSI_Keypad8] = Key.NumPad8, + [kVK_ANSI_Keypad9] = Key.NumPad9, + [kVK_Return] = Key.Return, + [kVK_Tab] = Key.Tab, + [kVK_Space] = Key.Space, + [kVK_Delete] = Key.Delete, + [kVK_Escape] = Key.Escape, + [kVK_Command] = Key.LWin, + [kVK_Shift] = Key.LeftShift, + [kVK_CapsLock] = Key.CapsLock, + [kVK_Option] = Key.LeftAlt, + [kVK_Control] = Key.LeftCtrl, + [kVK_RightCommand] = Key.RWin, + [kVK_RightShift] = Key.RightShift, + [kVK_RightOption] = Key.RightAlt, + [kVK_RightControl] = Key.RightCtrl, + //[kVK_Function] = Key.?, + [kVK_F17] = Key.F17, + [kVK_VolumeUp] = Key.VolumeUp, + [kVK_VolumeDown] = Key.VolumeDown, + [kVK_Mute] = Key.VolumeMute, + [kVK_F18] = Key.F18, + [kVK_F19] = Key.F19, + [kVK_F20] = Key.F20, + [kVK_F5] = Key.F5, + [kVK_F6] = Key.F6, + [kVK_F7] = Key.F7, + [kVK_F3] = Key.F3, + [kVK_F8] = Key.F8, + [kVK_F9] = Key.F9, + [kVK_F11] = Key.F11, + [kVK_F13] = Key.F13, + [kVK_F16] = Key.F16, + [kVK_F14] = Key.F14, + [kVK_F10] = Key.F10, + [kVK_F12] = Key.F12, + [kVK_F15] = Key.F15, + [kVK_Help] = Key.Help, + [kVK_Home] = Key.Home, + [kVK_PageUp] = Key.PageUp, + [kVK_ForwardDelete] = Key.Delete, + [kVK_F4] = Key.F4, + [kVK_End] = Key.End, + [kVK_F2] = Key.F2, + [kVK_PageDown] = Key.PageDown, + [kVK_F1] = Key.F1, + [kVK_LeftArrow] = Key.Left, + [kVK_RightArrow] = Key.Right, + [kVK_DownArrow] = Key.Down, + [kVK_UpArrow] = Key.Up, + /* + [kVK_ISO_Section] = Key.?, + [kVK_JIS_Yen] = Key.?, + [kVK_JIS_Underscore] = Key.?, + [kVK_JIS_KeypadComma] = Key.?, + [kVK_JIS_Eisu] = Key.?, + [kVK_JIS_Kana] = Key.? + */ + }; + + + public static Key? TransformKeyCode(ushort code) + { + Key rv; + if (Keys.TryGetValue(code, out rv)) + return rv; + return null; + } + } +} diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs new file mode 100644 index 0000000000..c3f07a2439 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -0,0 +1,77 @@ +using System; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Platform; +using Avalonia.Rendering; +using MonoMac.Foundation; +using MonoMac.AppKit; +using System.Drawing; + +namespace Avalonia.MonoMac +{ + public class MonoMacPlatform : IWindowingPlatform, IPlatformSettings + { + internal static MonoMacPlatform Instance { get; private set; } + MouseDevice _mouseDevice = new MouseDevice(); + KeyboardDevice _keyboardDevice = new KeyboardDevice(); + NSApplication _app; + void DoInitialize() + { + AvaloniaLocator.CurrentMutable + .Bind().ToTransient() + .Bind().ToSingleton() + .Bind().ToConstant(_keyboardDevice) + .Bind().ToConstant(_mouseDevice) + .Bind().ToConstant(this) + .Bind().ToConstant(ImmediateRenderer.Factory) + .Bind().ToConstant(this) + .Bind().ToConstant(PlatformThreadingInterface.Instance); + + InitializeCocoaApp(); + } + + public static void Initialize() + { + Instance = new MonoMacPlatform(); + Instance.DoInitialize(); + + } + + void InitializeCocoaApp() + { + NSApplication.Init(); + _app = NSApplication.SharedApplication; + _app.ActivationPolicy = NSApplicationActivationPolicy.Regular; + + } + + + public Size DoubleClickSize => new Size(4, 4); + public TimeSpan DoubleClickTime => TimeSpan.FromSeconds(NSEvent.DoubleClickInterval); + + public IWindowImpl CreateWindow() => new WindowImpl(); + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + throw new PlatformNotSupportedException(); + } + + public IPopupImpl CreatePopup() + { + return new PopupImpl(); + } + } +} + + +namespace Avalonia +{ + public static class MonoMacPlatformExtensions + { + public static AppBuilderBase UseMonoMac(this AppBuilderBase builder) where T : AppBuilderBase, new() + { + return builder.UseWindowingSubsystem(Avalonia.MonoMac.MonoMacPlatform.Initialize); + } + } +} + \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs new file mode 100644 index 0000000000..4d15a164e0 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs @@ -0,0 +1,65 @@ +using System; +using System.Threading; +using Avalonia.Platform; +using MonoMac.AppKit; +using MonoMac.CoreGraphics; +using MonoMac.Foundation; + +namespace Avalonia.MonoMac +{ + class PlatformThreadingInterface : IPlatformThreadingInterface + { + private bool _signaled; + public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); + public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; + + public event Action Signaled; + + public IDisposable StartTimer(TimeSpan interval, Action tick) + => NSTimer.CreateRepeatingScheduledTimer(interval, () => tick()); + + public void Signal() + { + lock (this) + { + if (_signaled) + return; + _signaled = true; + } + NSApplication.SharedApplication.BeginInvokeOnMainThread(() => + { + lock(this) + { + if (!_signaled) + return; + _signaled = false; + } + Signaled?.Invoke(); + }); + } + + + + public void RunLoop(CancellationToken cancellationToken) + { + NSApplication.SharedApplication.ActivateIgnoringOtherApps(true); + //NSApplication.SharedApplication.Run(); + + var app = NSApplication.SharedApplication; + cancellationToken.Register(() => + { + app.PostEvent(NSEvent.OtherEvent(NSEventType.ApplicationDefined, default(CGPoint), + default(NSEventModifierMask), 0, 0, null, 0, 0, 0), true); + }); + while (!cancellationToken.IsCancellationRequested) + { + var ev = app.NextEvent(NSEventMask.AnyEvent, NSDate.DistantPast, NSRunLoop.NSDefaultRunLoopMode, true); + if (ev != null) + { + app.SendEvent(ev); + ev.Dispose(); + } + } + } + } +} diff --git a/src/OSX/Avalonia.MonoMac/PopupImpl.cs b/src/OSX/Avalonia.MonoMac/PopupImpl.cs new file mode 100644 index 0000000000..0b1ee537f3 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/PopupImpl.cs @@ -0,0 +1,19 @@ +using System; +using Avalonia.Platform; +using MonoMac.AppKit; + +namespace Avalonia.MonoMac +{ + class PopupImpl : WindowBaseImpl, IPopupImpl + { + public PopupImpl() + { + UpdateStyle(); + } + + protected override NSWindowStyle GetStyle() + { + return NSWindowStyle.Borderless; + } + } +} diff --git a/src/OSX/Avalonia.MonoMac/Stubs.cs b/src/OSX/Avalonia.MonoMac/Stubs.cs new file mode 100644 index 0000000000..ec7609fa4f --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/Stubs.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using Avalonia.Input; +using Avalonia.Platform; +using MonoMac.AppKit; + +namespace Avalonia.MonoMac +{ + // OSX doesn't have a concept of *window* icon. + // Icons in the title bar are only shown if there is + // an opened file (on disk) associated with the current window + // see http://stackoverflow.com/a/7038671/2231814 + class IconLoader : IPlatformIconLoader + { + class IconStub : IWindowIconImpl + { + private readonly IBitmapImpl _bitmap; + + public IconStub(IBitmapImpl bitmap) + { + _bitmap = bitmap; + } + + public void Save(Stream outputStream) + { + _bitmap.Save(outputStream); + } + } + + public IWindowIconImpl LoadIcon(string fileName) + { + return new IconStub( + AvaloniaLocator.Current.GetService().LoadBitmap(fileName)); + } + + public IWindowIconImpl LoadIcon(Stream stream) + { + return new IconStub( + AvaloniaLocator.Current.GetService().LoadBitmap(stream)); + } + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) + { + var ms = new MemoryStream(); + bitmap.Save(ms); + ms.Seek(0, SeekOrigin.Begin); + return LoadIcon(ms); + } + } + +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs new file mode 100644 index 0000000000..2854aba9c9 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections.Generic; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Platform; +using Avalonia.Controls.Platform.Surfaces; +using MonoMac.AppKit; + +using MonoMac.CoreGraphics; +using MonoMac.Foundation; +using MonoMac.ObjCRuntime; + +namespace Avalonia.MonoMac +{ + abstract class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface + { + public TopLevelView View { get; } + public TopLevelImpl() + { + View = new TopLevelView(this); + } + + [Adopts("NSTextInputClient")] + public class TopLevelView : NSView + { + TopLevelImpl _tl; + bool _isLeftPressed, _isRightPressed, _isMiddlePressed; + private readonly IMouseDevice _mouse; + private readonly IKeyboardDevice _keyboard; + private NSTrackingArea _area; + private NSCursor _cursor; + public TopLevelView(TopLevelImpl tl) + { + _tl = tl; + _mouse = AvaloniaLocator.Current.GetService(); + _keyboard = AvaloniaLocator.Current.GetService(); + } + + public override bool ConformsToProtocol(IntPtr protocol) + { + var rv = base.ConformsToProtocol(protocol); + return rv; + } + + public override void DrawRect(CGRect dirtyRect) + { + _tl.Paint?.Invoke(dirtyRect.ToAvaloniaRect()); + } + + [Export("viewDidChangeBackingProperties:")] + public void ViewDidChangeBackingProperties() + { + _tl?.ScalingChanged?.Invoke(_tl.Scaling); + } + + void UpdateCursor() + { + ResetCursorRects(); + if (_cursor != null) + AddCursorRect(Frame, _cursor); + } + + static NSCursor ArrowCursor = NSCursor.ArrowCursor; + public void SetCursor(NSCursor cursor) + { + _cursor = cursor ?? ArrowCursor; + UpdateCursor(); + } + + public override void SetFrameSize(CGSize newSize) + { + base.SetFrameSize(newSize); + + if (_area != null) + { + RemoveTrackingArea(_area); + _area.Dispose(); ; + } + _area = new NSTrackingArea(new CGRect(default(CGPoint), newSize), + NSTrackingAreaOptions.ActiveAlways | + NSTrackingAreaOptions.MouseMoved | + NSTrackingAreaOptions.EnabledDuringMouseDrag, this, null); + AddTrackingArea(_area); + UpdateCursor(); + _tl?.Resized?.Invoke(_tl.ClientSize); + } + + InputModifiers GetModifiers(NSEventModifierMask mod) + { + var rv = new InputModifiers(); + if (mod.HasFlag(NSEventModifierMask.ControlKeyMask)) + rv |= InputModifiers.Control; + if (mod.HasFlag(NSEventModifierMask.ShiftKeyMask)) + rv |= InputModifiers.Shift; + if (mod.HasFlag(NSEventModifierMask.AlternateKeyMask)) + rv |= InputModifiers.Alt; + if (mod.HasFlag(NSEventModifierMask.CommandKeyMask)) + rv |= InputModifiers.Windows; + + if (_isLeftPressed) + rv |= InputModifiers.LeftMouseButton; + if (_isMiddlePressed) + rv |= InputModifiers.MiddleMouseButton; + if (_isRightPressed) + rv |= InputModifiers.RightMouseButton; + return rv; + } + + public Point TranslateLocalPoint(Point pt) => pt.WithY(Bounds.Height - pt.Y); + + Vector GetDelta(NSEvent ev) + { + var rv = new Vector(ev.ScrollingDeltaX, ev.ScrollingDeltaY); + //TODO: Verify if handling of HasPreciseScrollingDeltas + // is required (touchpad or magic-mouse is needed) + return rv; + } + + uint GetTimeStamp(NSEvent ev) => (uint)(ev.Timestamp * 1000); + + void MouseEvent(NSEvent ev, RawMouseEventType type) + { + BecomeFirstResponder(); + var loc = TranslateLocalPoint(ConvertPointToView(ev.LocationInWindow, this).ToAvaloniaPoint()); + var ts = GetTimeStamp(ev); + var mod = GetModifiers(ev.ModifierFlags); + if (type == RawMouseEventType.Wheel) + { + var delta = GetDelta(ev); + if (delta.X == 0 && delta.Y == 0) + return; + _tl.Input?.Invoke(new RawMouseWheelEventArgs(_mouse, ts, _tl.InputRoot, loc, + delta, mod)); + } + else + _tl.Input?.Invoke(new RawMouseEventArgs(_mouse, ts, _tl.InputRoot, type, loc, mod)); + } + + public override void MouseMoved(NSEvent theEvent) + { + MouseEvent(theEvent, RawMouseEventType.Move); + base.MouseMoved(theEvent); + } + + public override void MouseDragged(NSEvent theEvent) + { + MouseEvent(theEvent, RawMouseEventType.Move); + base.MouseDragged(theEvent); + } + + public override void OtherMouseDragged(NSEvent theEvent) + { + MouseEvent(theEvent, RawMouseEventType.Move); + base.OtherMouseDragged(theEvent); + } + + public override void RightMouseDragged(NSEvent theEvent) + { + MouseEvent(theEvent, RawMouseEventType.Move); + base.RightMouseDragged(theEvent); + } + + public NSEvent LastMouseDownEvent { get; private set; } + + public override void MouseDown(NSEvent theEvent) + { + _isLeftPressed = true; + LastMouseDownEvent = theEvent; + MouseEvent(theEvent, RawMouseEventType.LeftButtonDown); + LastMouseDownEvent = null; + base.MouseDown(theEvent); + } + + public override void RightMouseDown(NSEvent theEvent) + { + _isRightPressed = true; + MouseEvent(theEvent, RawMouseEventType.RightButtonDown); + base.RightMouseDown(theEvent); + } + + public override void OtherMouseDown(NSEvent theEvent) + { + _isMiddlePressed = true; + MouseEvent(theEvent, RawMouseEventType.MiddleButtonDown); + base.OtherMouseDown(theEvent); + } + + public override void MouseUp(NSEvent theEvent) + { + _isLeftPressed = false; + MouseEvent(theEvent, RawMouseEventType.LeftButtonUp); + base.MouseUp(theEvent); + } + + public override void RightMouseUp(NSEvent theEvent) + { + _isRightPressed = false; + MouseEvent(theEvent, RawMouseEventType.RightButtonUp); + base.RightMouseUp(theEvent); + } + + public override void OtherMouseUp(NSEvent theEvent) + { + _isMiddlePressed = false; + MouseEvent(theEvent, RawMouseEventType.MiddleButtonUp); + base.OtherMouseUp(theEvent); + } + + public override void ScrollWheel(NSEvent theEvent) + { + MouseEvent(theEvent, RawMouseEventType.Wheel); + base.ScrollWheel(theEvent); + } + + public override void MouseExited(NSEvent theEvent) + { + MouseEvent(theEvent, RawMouseEventType.LeaveWindow); + base.MouseExited(theEvent); + } + + void KeyboardEvent(RawKeyEventType type, NSEvent ev) + { + var code = KeyTransform.TransformKeyCode(ev.KeyCode); + if (!code.HasValue) + return; + _tl.Input?.Invoke(new RawKeyEventArgs(_keyboard, GetTimeStamp(ev), + type, code.Value, GetModifiers(ev.ModifierFlags))); + } + + public override void KeyDown(NSEvent theEvent) + { + KeyboardEvent(RawKeyEventType.KeyDown, theEvent); + InputContext.HandleEvent(theEvent); + base.KeyDown(theEvent); + } + + public override void KeyUp(NSEvent theEvent) + { + KeyboardEvent(RawKeyEventType.KeyUp, theEvent); + base.KeyUp(theEvent); + } + + + + #region NSTextInputClient + + public override bool AcceptsFirstResponder() => true; + + public bool HasMarkedText + { + [Export("hasMarkedText")] + get { return false; } + } + + public NSRange MarkedRange + { + [Export("markedRange")] + get { return new NSRange(NSRange.NotFound, 0); } + } + + public NSRange SelectedRange + { + [Export("selectedRange")] + get { return new NSRange(NSRange.NotFound, 0); } + } + + [Export("setMarkedText:selectedRange:replacementRange:")] + public void SetMarkedText(NSString str, NSRange a1, NSRange a2) + { + + } + + [Export("unmarkText")] + public void UnmarkText() + { + + } + + public NSArray ValidAttributesForMarkedText + { + [Export("validAttributesForMarkedText")] + get + { + return new NSArray(); + } + } + + [Export("attributedSubstringForProposedRange:actualRange:")] + public NSAttributedString AttributedSubstringForProposedRange(NSRange range, IntPtr wat) + { + return new NSAttributedString(""); + } + + [Export("insertText:replacementRange:")] + public void InsertText(NSString str, NSRange range) + { + //TODO: timestamp + _tl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, 0, str.ToString())); + } + + [Export("characterIndexForPoint:")] + public uint CharacterIndexForPoint(CGPoint pt) + { + return 0; + } + + [Export("firstRectForCharacterRange:actualRange:")] + public CGRect FirstRectForCharacterRange(NSRange range, IntPtr wat) + { + return new CGRect(); + } + + #endregion + } + + public IInputRoot InputRoot { get; private set; } + + public abstract Size ClientSize { get; } + + public double Scaling + { + get + { + if (View.Window == null) + return 1; + return View.Window.BackingScaleFactor; + } + } + + public IEnumerable Surfaces => new[] { this }; + + #region Events + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action Closed { get; set; } + #endregion + + public virtual void Dispose() + { + Closed?.Invoke(); + Closed = null; + View.Dispose(); + } + + public void Invalidate(Rect rect) + { + View.SetNeedsDisplayInRect(View.Frame); + } + + public abstract Point PointToClient(Point point); + + public abstract Point PointToScreen(Point point); + + public void SetCursor(IPlatformHandle cursor) + { + View.SetCursor((cursor as Cursor)?.Native); + } + + public void SetInputRoot(IInputRoot inputRoot) + { + InputRoot = inputRoot; + } + + public ILockedFramebuffer Lock() => new EmulatedFramebuffer(View); + } +} diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs new file mode 100644 index 0000000000..9186d3ded5 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Platform; +using MonoMac.AppKit; +using MonoMac.ObjCRuntime; + +namespace Avalonia.MonoMac +{ + class WindowBaseImpl : TopLevelImpl, IWindowBaseImpl + { + public CustomWindow Window { get; private set; } + + public WindowBaseImpl() + { + Window = new CustomWindow(this); + Window.StyleMask = NSWindowStyle.Titled; + Window.BackingType = NSBackingStore.Buffered; + Window.ContentView = View; + Window.Delegate = CreateWindowDelegate(); + } + + public class CustomWindow : NSWindow + { + readonly WindowBaseImpl _impl; + + public CustomWindow(WindowBaseImpl impl) + { + _impl = impl; + } + + public override void BecomeKeyWindow() + { + _impl.Activated?.Invoke(); + base.BecomeKeyWindow(); + } + + public override void ResignKeyWindow() + { + _impl.Deactivated?.Invoke(); + base.ResignKeyWindow(); + } + } + + protected virtual NSWindowDelegate CreateWindowDelegate() => new WindowBaseDelegate(this); + + public class WindowBaseDelegate : NSWindowDelegate + { + readonly WindowBaseImpl _impl; + + public WindowBaseDelegate(WindowBaseImpl impl) + { + _impl = impl; + } + + public override void DidMoved(global::MonoMac.Foundation.NSNotification notification) + { + _impl.PositionChanged?.Invoke(_impl.Position); + } + + public override void WillClose(global::MonoMac.Foundation.NSNotification notification) + { + _impl.Window.Dispose(); + _impl.Window = null; + _impl.Dispose(); + } + } + + + public Point Position + { + get + { + var pos = Window.Frame.ToAvaloniaRect().BottomLeft.ConvertPointY(); + //Console.WriteLine($"GET pos {pos}"); + return pos; + } + set + { + //Console.WriteLine($"SET pos {value}"); + Window.CascadeTopLeftFromPoint(value.ToMonoMacPoint().ConvertPointY()); + } + } + + + protected virtual NSWindowStyle GetStyle() + { + return NSWindowStyle.Borderless; + } + + protected void UpdateStyle() + { + Window.StyleMask = GetStyle(); + } + + + IPlatformHandle IWindowBaseImpl.Handle => new PlatformHandle(Window.Handle, "NSWindow"); + public Size MaxClientSize => NSScreen.Screens[0].Frame.ToAvaloniaRect().Size; + public Action PositionChanged { get; set; } + public Action Deactivated { get; set; } + public Action Activated { get; set; } + + public override Size ClientSize => Window.ContentRectFor(Window.Frame).Size.ToAvaloniaSize(); + + + public void Show() + { + Window.MakeKeyAndOrderFront(Window); + } + + public void Hide() + { + Window?.OrderOut(Window); + } + + + public void BeginMoveDrag() + { + var ev = View.LastMouseDownEvent; + if (ev == null) + return; + var handle = Selector.GetHandle("performWindowDragWithEvent:"); + Messaging.void_objc_msgSend_IntPtr(Window.Handle, handle, ev.Handle); + } + + public void BeginResizeDrag(WindowEdge edge) + { + //TODO: Intercept mouse events and implement resize drag manually + } + + public void Activate() + { + Window.MakeKeyWindow(); + } + + public void Resize(Size clientSize) + { + var pos = Position; + Window.SetContentSize(clientSize.ToMonoMacSize()); + Position = pos; + } + + public override Point PointToClient(Point point) + { + var cocoaScreenPoint = point.ToMonoMacPoint().ConvertPointY(); + var cocoaViewPoint = Window.ConvertScreenToBase(cocoaScreenPoint).ToAvaloniaPoint(); + return View.TranslateLocalPoint(cocoaViewPoint); + } + + public override Point PointToScreen(Point point) + { + var cocoaViewPoint = View.TranslateLocalPoint(point).ToMonoMacPoint(); + var cocoaScreenPoint = Window.ConvertBaseToScreen(cocoaViewPoint); + return cocoaScreenPoint.ConvertPointY().ToAvaloniaPoint(); + } + + + + public override void Dispose() + { + Window?.Close(); + Window?.Dispose(); + base.Dispose(); + } + } +} diff --git a/src/OSX/Avalonia.MonoMac/WindowImpl.cs b/src/OSX/Avalonia.MonoMac/WindowImpl.cs new file mode 100644 index 0000000000..bef24cc6b1 --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/WindowImpl.cs @@ -0,0 +1,92 @@ +using System; +using Avalonia.Controls; +using Avalonia.Platform; +using MonoMac.AppKit; + +namespace Avalonia.MonoMac +{ + class WindowImpl : WindowBaseImpl, IWindowImpl + { + bool _decorated = true; + public WindowImpl() + { + UpdateStyle(); + } + + public WindowState WindowState + { + get + { + if (Window.IsMiniaturized) + return WindowState.Minimized; + return WindowState.Normal; + + } + set + { + if (value == WindowState.Maximized) + { + if (Window.IsMiniaturized) + Window.Deminiaturize(Window); + if (!Window.IsZoomed) + Window.PerformZoom(Window); + } + else if (value.HasFlag(WindowState.Minimized)) + Window.Miniaturize(Window); + else + { + if (Window.IsMiniaturized) + Window.Deminiaturize(Window); + if (Window.IsZoomed) + Window.IsZoomed = false; + } + } + } + + public void SetIcon(IWindowIconImpl icon) + { + //No-OP, see http://stackoverflow.com/a/7038671/2231814 + } + + protected override NSWindowStyle GetStyle() + { + if (_decorated) + return NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable | NSWindowStyle.Titled; + return NSWindowStyle.Borderless; + } + + public void SetSystemDecorations(bool enabled) + { + _decorated = true; + UpdateStyle(); + } + + public void SetTitle(string title) + { + Window.Title = title; + } + + class ModalDisposable : IDisposable + { + readonly WindowImpl impl; + + public ModalDisposable(WindowImpl impl) + { + this.impl = impl; + } + + public void Dispose() + { + impl.Window.OrderOut(impl.Window); + } + } + + public IDisposable ShowDialog() + { + //TODO: Investigate how to return immediately. + // May be add some magic to our run loop or something + NSApplication.SharedApplication.RunModalForWindow(Window); + return new ModalDisposable(this); + } + } +} From f13f017057af29ed380360b3ed36b578609bf286 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 1 Jun 2017 13:11:05 +0300 Subject: [PATCH 002/110] [MONOMAC] Code cleanup --- src/OSX/Avalonia.MonoMac/.gitignore | 1 + src/OSX/Avalonia.MonoMac/Cursor.cs | 19 +- .../Avalonia.MonoMac/EmulatedFramebuffer.cs | 90 +++---- src/OSX/Avalonia.MonoMac/Helpers.cs | 30 +-- src/OSX/Avalonia.MonoMac/KeyTransform.cs | 241 +++++++++--------- src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs | 15 +- .../PlatformThreadingInterface.cs | 34 +-- src/OSX/Avalonia.MonoMac/PopupImpl.cs | 11 +- src/OSX/Avalonia.MonoMac/Stubs.cs | 10 +- src/OSX/Avalonia.MonoMac/TopLevelImpl.cs | 99 ++++--- src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs | 70 ++--- src/OSX/Avalonia.MonoMac/WindowImpl.cs | 21 +- 12 files changed, 289 insertions(+), 352 deletions(-) create mode 100644 src/OSX/Avalonia.MonoMac/.gitignore diff --git a/src/OSX/Avalonia.MonoMac/.gitignore b/src/OSX/Avalonia.MonoMac/.gitignore new file mode 100644 index 0000000000..2d6e7a861f --- /dev/null +++ b/src/OSX/Avalonia.MonoMac/.gitignore @@ -0,0 +1 @@ +Avalonia.MonoMac.sln diff --git a/src/OSX/Avalonia.MonoMac/Cursor.cs b/src/OSX/Avalonia.MonoMac/Cursor.cs index 8f5f2c2063..10445e62e2 100644 --- a/src/OSX/Avalonia.MonoMac/Cursor.cs +++ b/src/OSX/Avalonia.MonoMac/Cursor.cs @@ -21,9 +21,10 @@ namespace Avalonia.MonoMac } } - class CursorFactoryStub : IStandardCursorFactory - { + class CursorFactoryStub : IStandardCursorFactory + { Dictionary _cache; + public CursorFactoryStub() { //TODO: Load diagonal cursors from webkit @@ -33,8 +34,8 @@ namespace Avalonia.MonoMac [StandardCursorType.Arrow] = NSCursor.ArrowCursor, [StandardCursorType.AppStarting] = NSCursor.ArrowCursor, //TODO [StandardCursorType.BottomLeftCorner] = NSCursor.CrosshairCursor, //TODO - [StandardCursorType.BottomRightCorner]= NSCursor.CrosshairCursor, //TODO - [StandardCursorType.BottomSize] = NSCursor.ResizeDownCursor, + [StandardCursorType.BottomRightCorner] = NSCursor.CrosshairCursor, //TODO + [StandardCursorType.BottomSize] = NSCursor.ResizeDownCursor, [StandardCursorType.Cross] = NSCursor.CrosshairCursor, [StandardCursorType.Hand] = NSCursor.PointingHandCursor, [StandardCursorType.Help] = NSCursor.ContextualMenuCursor, @@ -50,12 +51,12 @@ namespace Avalonia.MonoMac [StandardCursorType.TopSide] = NSCursor.ResizeUpCursor, [StandardCursorType.UpArrow] = NSCursor.ResizeUpCursor, [StandardCursorType.Wait] = NSCursor.ArrowCursor, //TODO - }; + }; } public IPlatformHandle GetCursor(StandardCursorType cursorType) - { + { return new Cursor(_cache[cursorType]); - } - } -} + } + } +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs index 11cc1e0151..e524a706cc 100644 --- a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs +++ b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs @@ -3,72 +3,50 @@ using Avalonia.Platform; using MonoMac.AppKit; using System.Runtime.InteropServices; using MonoMac.CoreGraphics; -using MonoMac.ImageIO; -using MonoMac.Foundation; namespace Avalonia.MonoMac { class EmulatedFramebuffer : ILockedFramebuffer - { - [DllImport("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/CoreGraphics.framework/CoreGraphics")] - extern static IntPtr CGBitmapContextCreate (IntPtr data, UIntPtr width, UIntPtr height, UIntPtr bitsPerComponent, - UIntPtr bytesPerRow, IntPtr colorSpace, uint bitmapInfo); - - public EmulatedFramebuffer(NSView view) - { + { + public EmulatedFramebuffer(NSView view) + { //TODO: Check if this is correct var factor = view.Window.UserSpaceScaleFactor; - var frame = view.Frame; - Width = (int)(frame.Width * factor); - Height = (int)(frame.Height * factor); - RowBytes = Width * 4; - Dpi = new Size(96, 96) * factor; + var frame = view.Frame; + Width = (int) (frame.Width * factor); + Height = (int) (frame.Height * factor); + RowBytes = Width * 4; + Dpi = new Size(96, 96) * factor; Format = PixelFormat.Rgba8888; - Address = Marshal.AllocHGlobal(Height * RowBytes); - } + Address = Marshal.AllocHGlobal(Height * RowBytes); + } - public void Dispose() - { - if (Address == IntPtr.Zero) - return; - var nfo = (int)CGBitmapFlags.ByteOrder32Big | (int)CGImageAlphaInfo.PremultipliedLast; + public void Dispose() + { + if (Address == IntPtr.Zero) + return; + var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast; using (var colorSpace = CGColorSpace.CreateDeviceRGB()) - using (var bContext = new CGBitmapContext (Address, Width, Height, 8, Width * 4, - colorSpace, (CGImageAlphaInfo)nfo)) - using (var image = bContext.ToImage()) + using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4, + colorSpace, (CGImageAlphaInfo) nfo)) + using (var image = bContext.ToImage()) using (var nscontext = NSGraphicsContext.CurrentContext) - using(var context = nscontext.GraphicsPort) - { - /* - var url = NSUrl.FromFilename("/tmp/wat.png"); - var dest = CGImageDestination.FromUrl(url, "public.png", 1); - dest.AddImage(image, new NSDictionary()); - dest.Close(); - dest.Dispose(); - - var buf = new byte[Height * RowBytes]; - Marshal.Copy(Address, buf, 0, buf.Length); - System.IO.File.WriteAllBytes("/tmp/wat.bin", buf); -*/ - // flip the image for CGContext.DrawImage - //context.TranslateCTM(0, Height); - //context.ScaleCTM(1, -1); - + using (var context = nscontext.GraphicsPort) + { context.SetFillColor(255, 255, 255, 255); context.FillRect(new CGRect(0, 0, Width, Height)); - - context.DrawImage(new CGRect(0, 0, Width, Height), image); - } - Marshal.FreeHGlobal(Address); - Address = IntPtr.Zero; - } - - public IntPtr Address { get; private set; } - public int Width { get; } - public int Height { get; } - public int RowBytes { get; } - public Size Dpi { get; } - public PixelFormat Format { get; } - } -} + context.DrawImage(new CGRect(0, 0, Width, Height), image); + } + Marshal.FreeHGlobal(Address); + Address = IntPtr.Zero; + } + + public IntPtr Address { get; private set; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + public Size Dpi { get; } + public PixelFormat Format { get; } + } +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/Helpers.cs b/src/OSX/Avalonia.MonoMac/Helpers.cs index 90fd88d725..b4e3f7e0b7 100644 --- a/src/OSX/Avalonia.MonoMac/Helpers.cs +++ b/src/OSX/Avalonia.MonoMac/Helpers.cs @@ -1,9 +1,7 @@ using System; -using System.Runtime.InteropServices; -using MonoMac.AppKit; -using MonoMac.ObjCRuntime; -using MonoMac.CoreGraphics; -using MonoMac; + using MonoMac.AppKit; + using MonoMac.CoreGraphics; + namespace Avalonia.MonoMac { static class Helpers @@ -15,19 +13,19 @@ namespace Avalonia.MonoMac public static Rect ToAvaloniaRect(this CGRect rect) => new Rect(rect.Left, rect.Top, rect.Width, rect.Height); public static CGRect ToMonoMacRect(this Rect rect) => new CGRect(rect.X, rect.Y, rect.Width, rect.Height); - public static Point ConvertPointY(this Point pt) - { - var sw = NSScreen.Screens[0].Frame; - var t = Math.Max(sw.Top, sw.Bottom); - return pt.WithY(t - pt.Y); - } + public static Point ConvertPointY(this Point pt) + { + var sw = NSScreen.Screens[0].Frame; + var t = Math.Max(sw.Top, sw.Bottom); + return pt.WithY(t - pt.Y); + } - public static CGPoint ConvertPointY(this CGPoint pt) - { - var sw = NSScreen.Screens[0].Frame; - var t = Math.Max(sw.Top, sw.Bottom); + public static CGPoint ConvertPointY(this CGPoint pt) + { + var sw = NSScreen.Screens[0].Frame; + var t = Math.Max(sw.Top, sw.Bottom); return new CGPoint(pt.X, t - pt.Y); - } + } } } diff --git a/src/OSX/Avalonia.MonoMac/KeyTransform.cs b/src/OSX/Avalonia.MonoMac/KeyTransform.cs index 930b6f7b81..6d4b58031e 100644 --- a/src/OSX/Avalonia.MonoMac/KeyTransform.cs +++ b/src/OSX/Avalonia.MonoMac/KeyTransform.cs @@ -1,14 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Avalonia.Input; -using MonoMac.AppKit; namespace Avalonia.MonoMac { public static class KeyTransform { - // See /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h - private const int kVK_ANSI_A = 0x00; + // See /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h + // ReSharper disable InconsistentNaming + // ReSharper disable UnusedMember.Local + private const int kVK_ANSI_A = 0x00; private const int kVK_ANSI_S = 0x01; private const int kVK_ANSI_D = 0x02; private const int kVK_ANSI_F = 0x03; @@ -127,130 +127,131 @@ namespace Avalonia.MonoMac private const int kVK_JIS_KeypadComma = 0x5F; private const int kVK_JIS_Eisu = 0x66; private const int kVK_JIS_Kana = 0x68; - + // ReSharper restore UnusedMember.Local + // ReSharper restore InconsistentNaming //TODO: Map missing keys static readonly Dictionary Keys = new Dictionary { - [kVK_ANSI_A] = Key.A, - [kVK_ANSI_S] = Key.S, - [kVK_ANSI_D] = Key.D, - [kVK_ANSI_F] = Key.F, - [kVK_ANSI_H] = Key.H, - [kVK_ANSI_G] = Key.G, - [kVK_ANSI_Z] = Key.Z, - [kVK_ANSI_X] = Key.X, - [kVK_ANSI_C] = Key.C, - [kVK_ANSI_V] = Key.V, - [kVK_ANSI_B] = Key.B, - [kVK_ANSI_Q] = Key.Q, - [kVK_ANSI_W] = Key.W, - [kVK_ANSI_E] = Key.E, - [kVK_ANSI_R] = Key.R, - [kVK_ANSI_Y] = Key.Y, - [kVK_ANSI_T] = Key.T, + [kVK_ANSI_A] = Key.A, + [kVK_ANSI_S] = Key.S, + [kVK_ANSI_D] = Key.D, + [kVK_ANSI_F] = Key.F, + [kVK_ANSI_H] = Key.H, + [kVK_ANSI_G] = Key.G, + [kVK_ANSI_Z] = Key.Z, + [kVK_ANSI_X] = Key.X, + [kVK_ANSI_C] = Key.C, + [kVK_ANSI_V] = Key.V, + [kVK_ANSI_B] = Key.B, + [kVK_ANSI_Q] = Key.Q, + [kVK_ANSI_W] = Key.W, + [kVK_ANSI_E] = Key.E, + [kVK_ANSI_R] = Key.R, + [kVK_ANSI_Y] = Key.Y, + [kVK_ANSI_T] = Key.T, [kVK_ANSI_1] = Key.D1, - [kVK_ANSI_2] = Key.D2, - [kVK_ANSI_3] = Key.D3, - [kVK_ANSI_4] = Key.D4, - [kVK_ANSI_6] = Key.D6, - [kVK_ANSI_5] = Key.D5, - //[kVK_ANSI_Equal] = Key.?, - [kVK_ANSI_9] = Key.D9, - [kVK_ANSI_7] = Key.D7, - [kVK_ANSI_Minus] = Key.OemMinus, - [kVK_ANSI_8] = Key.D8, - [kVK_ANSI_0] = Key.D0, + [kVK_ANSI_2] = Key.D2, + [kVK_ANSI_3] = Key.D3, + [kVK_ANSI_4] = Key.D4, + [kVK_ANSI_6] = Key.D6, + [kVK_ANSI_5] = Key.D5, + //[kVK_ANSI_Equal] = Key.?, + [kVK_ANSI_9] = Key.D9, + [kVK_ANSI_7] = Key.D7, + [kVK_ANSI_Minus] = Key.OemMinus, + [kVK_ANSI_8] = Key.D8, + [kVK_ANSI_0] = Key.D0, [kVK_ANSI_RightBracket] = Key.OemCloseBrackets, - [kVK_ANSI_O] = Key.O, - [kVK_ANSI_U] = Key.U, + [kVK_ANSI_O] = Key.O, + [kVK_ANSI_U] = Key.U, [kVK_ANSI_LeftBracket] = Key.OemOpenBrackets, - [kVK_ANSI_I] = Key.I, - [kVK_ANSI_P] = Key.P, - [kVK_ANSI_L] = Key.L, - [kVK_ANSI_J] = Key.J, - [kVK_ANSI_Quote] = Key.OemQuotes, - [kVK_ANSI_K] = Key.K, - [kVK_ANSI_Semicolon] = Key.OemSemicolon, - [kVK_ANSI_Backslash] = Key.OemBackslash, - [kVK_ANSI_Comma] = Key.OemComma, - //[kVK_ANSI_Slash] = Key.?, - [kVK_ANSI_N] = Key.N, - [kVK_ANSI_M] = Key.M, - [kVK_ANSI_Period] = Key.OemPeriod, - //[kVK_ANSI_Grave] = Key.?, - [kVK_ANSI_KeypadDecimal] = Key.Decimal, - [kVK_ANSI_KeypadMultiply] = Key.Multiply, - [kVK_ANSI_KeypadPlus] = Key.OemPlus, - [kVK_ANSI_KeypadClear] = Key.Clear, - [kVK_ANSI_KeypadDivide] = Key.Divide, - [kVK_ANSI_KeypadEnter] = Key.Enter, - [kVK_ANSI_KeypadMinus] = Key.OemMinus, - //[kVK_ANSI_KeypadEquals] = Key.?, - [kVK_ANSI_Keypad0] = Key.NumPad0, - [kVK_ANSI_Keypad1] = Key.NumPad1, - [kVK_ANSI_Keypad2] = Key.NumPad2, - [kVK_ANSI_Keypad3] = Key.NumPad3, - [kVK_ANSI_Keypad4] = Key.NumPad4, - [kVK_ANSI_Keypad5] = Key.NumPad5, - [kVK_ANSI_Keypad6] = Key.NumPad6, - [kVK_ANSI_Keypad7] = Key.NumPad7, - [kVK_ANSI_Keypad8] = Key.NumPad8, - [kVK_ANSI_Keypad9] = Key.NumPad9, - [kVK_Return] = Key.Return, - [kVK_Tab] = Key.Tab, - [kVK_Space] = Key.Space, - [kVK_Delete] = Key.Delete, - [kVK_Escape] = Key.Escape, + [kVK_ANSI_I] = Key.I, + [kVK_ANSI_P] = Key.P, + [kVK_ANSI_L] = Key.L, + [kVK_ANSI_J] = Key.J, + [kVK_ANSI_Quote] = Key.OemQuotes, + [kVK_ANSI_K] = Key.K, + [kVK_ANSI_Semicolon] = Key.OemSemicolon, + [kVK_ANSI_Backslash] = Key.OemBackslash, + [kVK_ANSI_Comma] = Key.OemComma, + //[kVK_ANSI_Slash] = Key.?, + [kVK_ANSI_N] = Key.N, + [kVK_ANSI_M] = Key.M, + [kVK_ANSI_Period] = Key.OemPeriod, + //[kVK_ANSI_Grave] = Key.?, + [kVK_ANSI_KeypadDecimal] = Key.Decimal, + [kVK_ANSI_KeypadMultiply] = Key.Multiply, + [kVK_ANSI_KeypadPlus] = Key.OemPlus, + [kVK_ANSI_KeypadClear] = Key.Clear, + [kVK_ANSI_KeypadDivide] = Key.Divide, + [kVK_ANSI_KeypadEnter] = Key.Enter, + [kVK_ANSI_KeypadMinus] = Key.OemMinus, + //[kVK_ANSI_KeypadEquals] = Key.?, + [kVK_ANSI_Keypad0] = Key.NumPad0, + [kVK_ANSI_Keypad1] = Key.NumPad1, + [kVK_ANSI_Keypad2] = Key.NumPad2, + [kVK_ANSI_Keypad3] = Key.NumPad3, + [kVK_ANSI_Keypad4] = Key.NumPad4, + [kVK_ANSI_Keypad5] = Key.NumPad5, + [kVK_ANSI_Keypad6] = Key.NumPad6, + [kVK_ANSI_Keypad7] = Key.NumPad7, + [kVK_ANSI_Keypad8] = Key.NumPad8, + [kVK_ANSI_Keypad9] = Key.NumPad9, + [kVK_Return] = Key.Return, + [kVK_Tab] = Key.Tab, + [kVK_Space] = Key.Space, + [kVK_Delete] = Key.Delete, + [kVK_Escape] = Key.Escape, [kVK_Command] = Key.LWin, - [kVK_Shift] = Key.LeftShift, - [kVK_CapsLock] = Key.CapsLock, + [kVK_Shift] = Key.LeftShift, + [kVK_CapsLock] = Key.CapsLock, [kVK_Option] = Key.LeftAlt, - [kVK_Control] = Key.LeftCtrl, - [kVK_RightCommand] = Key.RWin, - [kVK_RightShift] = Key.RightShift, - [kVK_RightOption] = Key.RightAlt, - [kVK_RightControl] = Key.RightCtrl, - //[kVK_Function] = Key.?, - [kVK_F17] = Key.F17, - [kVK_VolumeUp] = Key.VolumeUp, - [kVK_VolumeDown] = Key.VolumeDown, - [kVK_Mute] = Key.VolumeMute, - [kVK_F18] = Key.F18, - [kVK_F19] = Key.F19, - [kVK_F20] = Key.F20, - [kVK_F5] = Key.F5, - [kVK_F6] = Key.F6, - [kVK_F7] = Key.F7, - [kVK_F3] = Key.F3, - [kVK_F8] = Key.F8, - [kVK_F9] = Key.F9, - [kVK_F11] = Key.F11, - [kVK_F13] = Key.F13, - [kVK_F16] = Key.F16, - [kVK_F14] = Key.F14, - [kVK_F10] = Key.F10, - [kVK_F12] = Key.F12, - [kVK_F15] = Key.F15, - [kVK_Help] = Key.Help, - [kVK_Home] = Key.Home, - [kVK_PageUp] = Key.PageUp, - [kVK_ForwardDelete] = Key.Delete, - [kVK_F4] = Key.F4, - [kVK_End] = Key.End, - [kVK_F2] = Key.F2, - [kVK_PageDown] = Key.PageDown, - [kVK_F1] = Key.F1, - [kVK_LeftArrow] = Key.Left, - [kVK_RightArrow] = Key.Right, - [kVK_DownArrow] = Key.Down, - [kVK_UpArrow] = Key.Up, + [kVK_Control] = Key.LeftCtrl, + [kVK_RightCommand] = Key.RWin, + [kVK_RightShift] = Key.RightShift, + [kVK_RightOption] = Key.RightAlt, + [kVK_RightControl] = Key.RightCtrl, + //[kVK_Function] = Key.?, + [kVK_F17] = Key.F17, + [kVK_VolumeUp] = Key.VolumeUp, + [kVK_VolumeDown] = Key.VolumeDown, + [kVK_Mute] = Key.VolumeMute, + [kVK_F18] = Key.F18, + [kVK_F19] = Key.F19, + [kVK_F20] = Key.F20, + [kVK_F5] = Key.F5, + [kVK_F6] = Key.F6, + [kVK_F7] = Key.F7, + [kVK_F3] = Key.F3, + [kVK_F8] = Key.F8, + [kVK_F9] = Key.F9, + [kVK_F11] = Key.F11, + [kVK_F13] = Key.F13, + [kVK_F16] = Key.F16, + [kVK_F14] = Key.F14, + [kVK_F10] = Key.F10, + [kVK_F12] = Key.F12, + [kVK_F15] = Key.F15, + [kVK_Help] = Key.Help, + [kVK_Home] = Key.Home, + [kVK_PageUp] = Key.PageUp, + [kVK_ForwardDelete] = Key.Delete, + [kVK_F4] = Key.F4, + [kVK_End] = Key.End, + [kVK_F2] = Key.F2, + [kVK_PageDown] = Key.PageDown, + [kVK_F1] = Key.F1, + [kVK_LeftArrow] = Key.Left, + [kVK_RightArrow] = Key.Right, + [kVK_DownArrow] = Key.Down, + [kVK_UpArrow] = Key.Up, /* - [kVK_ISO_Section] = Key.?, - [kVK_JIS_Yen] = Key.?, - [kVK_JIS_Underscore] = Key.?, - [kVK_JIS_KeypadComma] = Key.?, - [kVK_JIS_Eisu] = Key.?, - [kVK_JIS_Kana] = Key.? + [kVK_ISO_Section] = Key.?, + [kVK_JIS_Yen] = Key.?, + [kVK_JIS_Underscore] = Key.?, + [kVK_JIS_KeypadComma] = Key.?, + [kVK_JIS_Eisu] = Key.?, + [kVK_JIS_Kana] = Key.? */ }; diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs index c3f07a2439..4ffb11ab94 100644 --- a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -3,17 +3,15 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Platform; using Avalonia.Rendering; -using MonoMac.Foundation; using MonoMac.AppKit; -using System.Drawing; namespace Avalonia.MonoMac { public class MonoMacPlatform : IWindowingPlatform, IPlatformSettings { internal static MonoMacPlatform Instance { get; private set; } - MouseDevice _mouseDevice = new MouseDevice(); - KeyboardDevice _keyboardDevice = new KeyboardDevice(); + readonly MouseDevice _mouseDevice = new MouseDevice(); + readonly KeyboardDevice _keyboardDevice = new KeyboardDevice(); NSApplication _app; void DoInitialize() { @@ -46,8 +44,8 @@ namespace Avalonia.MonoMac } - public Size DoubleClickSize => new Size(4, 4); - public TimeSpan DoubleClickTime => TimeSpan.FromSeconds(NSEvent.DoubleClickInterval); + public Size DoubleClickSize => new Size(4, 4); + public TimeSpan DoubleClickTime => TimeSpan.FromSeconds(NSEvent.DoubleClickInterval); public IWindowImpl CreateWindow() => new WindowImpl(); @@ -70,8 +68,7 @@ namespace Avalonia { public static AppBuilderBase UseMonoMac(this AppBuilderBase builder) where T : AppBuilderBase, new() { - return builder.UseWindowingSubsystem(Avalonia.MonoMac.MonoMacPlatform.Initialize); + return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize); } } -} - \ No newline at end of file +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs index 4d15a164e0..d926785229 100644 --- a/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs +++ b/src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs @@ -9,26 +9,26 @@ namespace Avalonia.MonoMac { class PlatformThreadingInterface : IPlatformThreadingInterface { - private bool _signaled; - public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); - public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; + private bool _signaled; + public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); + public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; - public event Action Signaled; + public event Action Signaled; public IDisposable StartTimer(TimeSpan interval, Action tick) - => NSTimer.CreateRepeatingScheduledTimer(interval, () => tick()); + => NSTimer.CreateRepeatingScheduledTimer(interval, () => tick()); - public void Signal() - { - lock (this) - { - if (_signaled) - return; - _signaled = true; - } + public void Signal() + { + lock (this) + { + if (_signaled) + return; + _signaled = true; + } NSApplication.SharedApplication.BeginInvokeOnMainThread(() => { - lock(this) + lock (this) { if (!_signaled) return; @@ -36,7 +36,7 @@ namespace Avalonia.MonoMac } Signaled?.Invoke(); }); - } + } @@ -49,7 +49,7 @@ namespace Avalonia.MonoMac cancellationToken.Register(() => { app.PostEvent(NSEvent.OtherEvent(NSEventType.ApplicationDefined, default(CGPoint), - default(NSEventModifierMask), 0, 0, null, 0, 0, 0), true); + default(NSEventModifierMask), 0, 0, null, 0, 0, 0), true); }); while (!cancellationToken.IsCancellationRequested) { @@ -62,4 +62,4 @@ namespace Avalonia.MonoMac } } } -} +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/PopupImpl.cs b/src/OSX/Avalonia.MonoMac/PopupImpl.cs index 0b1ee537f3..ba4b7f0eac 100644 --- a/src/OSX/Avalonia.MonoMac/PopupImpl.cs +++ b/src/OSX/Avalonia.MonoMac/PopupImpl.cs @@ -1,5 +1,4 @@ -using System; -using Avalonia.Platform; +using Avalonia.Platform; using MonoMac.AppKit; namespace Avalonia.MonoMac @@ -11,9 +10,9 @@ namespace Avalonia.MonoMac UpdateStyle(); } - protected override NSWindowStyle GetStyle() - { + protected override NSWindowStyle GetStyle() + { return NSWindowStyle.Borderless; - } + } } -} +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/Stubs.cs b/src/OSX/Avalonia.MonoMac/Stubs.cs index ec7609fa4f..f3ff6a260c 100644 --- a/src/OSX/Avalonia.MonoMac/Stubs.cs +++ b/src/OSX/Avalonia.MonoMac/Stubs.cs @@ -1,8 +1,5 @@ -using System; -using System.IO; -using Avalonia.Input; +using System.IO; using Avalonia.Platform; -using MonoMac.AppKit; namespace Avalonia.MonoMac { @@ -35,8 +32,8 @@ namespace Avalonia.MonoMac public IWindowIconImpl LoadIcon(Stream stream) { - return new IconStub( - AvaloniaLocator.Current.GetService().LoadBitmap(stream)); + return new IconStub( + AvaloniaLocator.Current.GetService().LoadBitmap(stream)); } public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) @@ -47,5 +44,4 @@ namespace Avalonia.MonoMac return LoadIcon(ms); } } - } \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs index 2854aba9c9..78c8434974 100644 --- a/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs +++ b/src/OSX/Avalonia.MonoMac/TopLevelImpl.cs @@ -15,7 +15,8 @@ namespace Avalonia.MonoMac abstract class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface { public TopLevelView View { get; } - public TopLevelImpl() + + protected TopLevelImpl() { View = new TopLevelView(this); } @@ -29,12 +30,13 @@ namespace Avalonia.MonoMac private readonly IKeyboardDevice _keyboard; private NSTrackingArea _area; private NSCursor _cursor; + public TopLevelView(TopLevelImpl tl) { _tl = tl; _mouse = AvaloniaLocator.Current.GetService(); _keyboard = AvaloniaLocator.Current.GetService(); - } + } public override bool ConformsToProtocol(IntPtr protocol) { @@ -60,7 +62,8 @@ namespace Avalonia.MonoMac AddCursorRect(Frame, _cursor); } - static NSCursor ArrowCursor = NSCursor.ArrowCursor; + static readonly NSCursor ArrowCursor = NSCursor.ArrowCursor; + public void SetCursor(NSCursor cursor) { _cursor = cursor ?? ArrowCursor; @@ -74,12 +77,12 @@ namespace Avalonia.MonoMac if (_area != null) { RemoveTrackingArea(_area); - _area.Dispose(); ; + _area.Dispose(); } _area = new NSTrackingArea(new CGRect(default(CGPoint), newSize), - NSTrackingAreaOptions.ActiveAlways | - NSTrackingAreaOptions.MouseMoved | - NSTrackingAreaOptions.EnabledDuringMouseDrag, this, null); + NSTrackingAreaOptions.ActiveAlways | + NSTrackingAreaOptions.MouseMoved | + NSTrackingAreaOptions.EnabledDuringMouseDrag, this, null); AddTrackingArea(_area); UpdateCursor(); _tl?.Resized?.Invoke(_tl.ClientSize); @@ -116,7 +119,7 @@ namespace Avalonia.MonoMac return rv; } - uint GetTimeStamp(NSEvent ev) => (uint)(ev.Timestamp * 1000); + uint GetTimeStamp(NSEvent ev) => (uint) (ev.Timestamp * 1000); void MouseEvent(NSEvent ev, RawMouseEventType type) { @@ -127,10 +130,12 @@ namespace Avalonia.MonoMac if (type == RawMouseEventType.Wheel) { var delta = GetDelta(ev); + // ReSharper disable CompareOfFloatsByEqualityOperator if (delta.X == 0 && delta.Y == 0) return; + // ReSharper restore CompareOfFloatsByEqualityOperator _tl.Input?.Invoke(new RawMouseWheelEventArgs(_mouse, ts, _tl.InputRoot, loc, - delta, mod)); + delta, mod)); } else _tl.Input?.Invoke(new RawMouseEventArgs(_mouse, ts, _tl.InputRoot, type, loc, mod)); @@ -224,7 +229,7 @@ namespace Avalonia.MonoMac if (!code.HasValue) return; _tl.Input?.Invoke(new RawKeyEventArgs(_keyboard, GetTimeStamp(ev), - type, code.Value, GetModifiers(ev.ModifierFlags))); + type, code.Value, GetModifiers(ev.ModifierFlags))); } public override void KeyDown(NSEvent theEvent) @@ -246,43 +251,36 @@ namespace Avalonia.MonoMac public override bool AcceptsFirstResponder() => true; - public bool HasMarkedText + public bool HasMarkedText { - [Export("hasMarkedText")] - get { return false; } + [Export("hasMarkedText")] get => false; } - public NSRange MarkedRange - { - [Export("markedRange")] - get { return new NSRange(NSRange.NotFound, 0); } - } + public NSRange MarkedRange + { + [Export("markedRange")] get => new NSRange(NSRange.NotFound, 0); + } public NSRange SelectedRange - { - [Export("selectedRange")] - get { return new NSRange(NSRange.NotFound, 0); } - } + { + [Export("selectedRange")] get => new NSRange(NSRange.NotFound, 0); + } [Export("setMarkedText:selectedRange:replacementRange:")] public void SetMarkedText(NSString str, NSRange a1, NSRange a2) { - + } [Export("unmarkText")] public void UnmarkText() { - + } public NSArray ValidAttributesForMarkedText { - [Export("validAttributesForMarkedText")] - get - { - return new NSArray(); - } + [Export("validAttributesForMarkedText")] get => new NSArray(); } [Export("attributedSubstringForProposedRange:actualRange:")] @@ -310,14 +308,14 @@ namespace Avalonia.MonoMac return new CGRect(); } - #endregion - } + #endregion + } public IInputRoot InputRoot { get; private set; } public abstract Size ClientSize { get; } - public double Scaling + public double Scaling { get { @@ -327,42 +325,35 @@ namespace Avalonia.MonoMac } } - public IEnumerable Surfaces => new[] { this }; + public IEnumerable Surfaces => new[] {this}; + + #region Events - #region Events - public Action Input { get; set; } - public Action Paint { get; set; } - public Action Resized { get; set; } - public Action ScalingChanged { get; set; } - public Action Closed { get; set; } - #endregion + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action Closed { get; set; } - public virtual void Dispose() + #endregion + + public virtual void Dispose() { Closed?.Invoke(); Closed = null; View.Dispose(); } - public void Invalidate(Rect rect) - { - View.SetNeedsDisplayInRect(View.Frame); - } + public void Invalidate(Rect rect) => View.SetNeedsDisplayInRect(View.Frame); public abstract Point PointToClient(Point point); public abstract Point PointToScreen(Point point); - public void SetCursor(IPlatformHandle cursor) - { - View.SetCursor((cursor as Cursor)?.Native); - } + public void SetCursor(IPlatformHandle cursor) => View.SetCursor((cursor as Cursor)?.Native); - public void SetInputRoot(IInputRoot inputRoot) - { - InputRoot = inputRoot; - } + public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; public ILockedFramebuffer Lock() => new EmulatedFramebuffer(View); } -} +} \ No newline at end of file diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index 9186d3ded5..cd53cb5dc0 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Input.Raw; using Avalonia.Platform; using MonoMac.AppKit; using MonoMac.ObjCRuntime; @@ -13,14 +10,17 @@ namespace Avalonia.MonoMac { public CustomWindow Window { get; private set; } - public WindowBaseImpl() - { - Window = new CustomWindow(this); - Window.StyleMask = NSWindowStyle.Titled; - Window.BackingType = NSBackingStore.Buffered; - Window.ContentView = View; - Window.Delegate = CreateWindowDelegate(); - } + public WindowBaseImpl() + { + Window = new CustomWindow(this) + { + StyleMask = NSWindowStyle.Titled, + BackingType = NSBackingStore.Buffered, + ContentView = View, + // ReSharper disable once VirtualMemberCallInConstructor + Delegate = CreateWindowDelegate() + }; + } public class CustomWindow : NSWindow { @@ -71,49 +71,28 @@ namespace Avalonia.MonoMac public Point Position { - get - { - var pos = Window.Frame.ToAvaloniaRect().BottomLeft.ConvertPointY(); - //Console.WriteLine($"GET pos {pos}"); - return pos; - } - set - { - //Console.WriteLine($"SET pos {value}"); - Window.CascadeTopLeftFromPoint(value.ToMonoMacPoint().ConvertPointY()); - } + get => Window.Frame.ToAvaloniaRect().BottomLeft.ConvertPointY(); + set => Window.CascadeTopLeftFromPoint(value.ToMonoMacPoint().ConvertPointY()); } - protected virtual NSWindowStyle GetStyle() - { - return NSWindowStyle.Borderless; - } + protected virtual NSWindowStyle GetStyle() => NSWindowStyle.Borderless; - protected void UpdateStyle() - { - Window.StyleMask = GetStyle(); - } + protected void UpdateStyle() => Window.StyleMask = GetStyle(); IPlatformHandle IWindowBaseImpl.Handle => new PlatformHandle(Window.Handle, "NSWindow"); public Size MaxClientSize => NSScreen.Screens[0].Frame.ToAvaloniaRect().Size; - public Action PositionChanged { get; set; } - public Action Deactivated { get; set; } - public Action Activated { get; set; } + public Action PositionChanged { get; set; } + public Action Deactivated { get; set; } + public Action Activated { get; set; } - public override Size ClientSize => Window.ContentRectFor(Window.Frame).Size.ToAvaloniaSize(); + public override Size ClientSize => Window.ContentRectFor(Window.Frame).Size.ToAvaloniaSize(); - public void Show() - { - Window.MakeKeyAndOrderFront(Window); - } + public void Show() => Window.MakeKeyAndOrderFront(Window); - public void Hide() - { - Window?.OrderOut(Window); - } + public void Hide() => Window?.OrderOut(Window); public void BeginMoveDrag() @@ -130,10 +109,7 @@ namespace Avalonia.MonoMac //TODO: Intercept mouse events and implement resize drag manually } - public void Activate() - { - Window.MakeKeyWindow(); - } + public void Activate() => Window.MakeKeyWindow(); public void Resize(Size clientSize) { @@ -162,7 +138,7 @@ namespace Avalonia.MonoMac { Window?.Close(); Window?.Dispose(); - base.Dispose(); + base.Dispose(); } } } diff --git a/src/OSX/Avalonia.MonoMac/WindowImpl.cs b/src/OSX/Avalonia.MonoMac/WindowImpl.cs index bef24cc6b1..dc6cca9979 100644 --- a/src/OSX/Avalonia.MonoMac/WindowImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowImpl.cs @@ -8,6 +8,7 @@ namespace Avalonia.MonoMac class WindowImpl : WindowBaseImpl, IWindowImpl { bool _decorated = true; + public WindowImpl() { UpdateStyle(); @@ -45,13 +46,14 @@ namespace Avalonia.MonoMac public void SetIcon(IWindowIconImpl icon) { - //No-OP, see http://stackoverflow.com/a/7038671/2231814 - } + //No-OP, see http://stackoverflow.com/a/7038671/2231814 + } protected override NSWindowStyle GetStyle() { if (_decorated) - return NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable | NSWindowStyle.Titled; + return NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable | + NSWindowStyle.Titled; return NSWindowStyle.Borderless; } @@ -61,23 +63,20 @@ namespace Avalonia.MonoMac UpdateStyle(); } - public void SetTitle(string title) - { - Window.Title = title; - } + public void SetTitle(string title) => Window.Title = title; class ModalDisposable : IDisposable { - readonly WindowImpl impl; + readonly WindowImpl _impl; public ModalDisposable(WindowImpl impl) { - this.impl = impl; + _impl = impl; } public void Dispose() { - impl.Window.OrderOut(impl.Window); + _impl.Window.OrderOut(_impl.Window); } } @@ -89,4 +88,4 @@ namespace Avalonia.MonoMac return new ModalDisposable(this); } } -} +} \ No newline at end of file From f7011a3fd26a1bdeb9f2db9ae8d7c08f4c5dd825 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 1 Jun 2017 22:47:19 +0300 Subject: [PATCH 003/110] [MONOMAC] Fixed retina support --- src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs index e524a706cc..0f33cdfa84 100644 --- a/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs +++ b/src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs @@ -8,15 +8,15 @@ namespace Avalonia.MonoMac { class EmulatedFramebuffer : ILockedFramebuffer { + private readonly CGSize _logicalSize; public EmulatedFramebuffer(NSView view) { - //TODO: Check if this is correct - var factor = view.Window.UserSpaceScaleFactor; - var frame = view.Frame; - Width = (int) (frame.Width * factor); - Height = (int) (frame.Height * factor); + _logicalSize = view.Frame.Size; + var pixelSize = view.ConvertSizeToBacking(_logicalSize); + Width = (int)pixelSize.Width; + Height = (int)pixelSize.Height; RowBytes = Width * 4; - Dpi = new Size(96, 96) * factor; + Dpi = new Size(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height); Format = PixelFormat.Rgba8888; Address = Marshal.AllocHGlobal(Height * RowBytes); } @@ -35,8 +35,8 @@ namespace Avalonia.MonoMac using (var context = nscontext.GraphicsPort) { context.SetFillColor(255, 255, 255, 255); - context.FillRect(new CGRect(0, 0, Width, Height)); - context.DrawImage(new CGRect(0, 0, Width, Height), image); + context.FillRect(new CGRect(default(CGPoint), _logicalSize)); + context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image); } Marshal.FreeHGlobal(Address); Address = IntPtr.Zero; From 12167dfea9254e3c22b76f8eabac0cbb37ebda1e Mon Sep 17 00:00:00 2001 From: "M. ter Woord" Date: Mon, 21 Aug 2017 18:22:04 +0200 Subject: [PATCH 004/110] Show descriptive error when no rendering or windowing subsystem found --- src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs b/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs index 9a54cdbab0..bae97f503d 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs +++ b/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs @@ -59,7 +59,11 @@ namespace Avalonia from attribute in assembly.GetCustomAttributes() where attribute.RequiredOS == os && CheckEnvironment(attribute.EnvironmentChecker) orderby attribute.Priority ascending - select attribute).First(); + select attribute).FirstOrDefault(); + if (windowingSubsystemAttribute == null) + { + throw new InvalidOperationException("No windowing subsystem found. Are you missing assembly references?"); + } var renderingSubsystemAttribute = (from assembly in RuntimePlatform.GetLoadedAssemblies() from attribute in assembly.GetCustomAttributes() @@ -67,7 +71,12 @@ namespace Avalonia where attribute.RequiresWindowingSubsystem == null || attribute.RequiresWindowingSubsystem == windowingSubsystemAttribute.Name orderby attribute.Priority ascending - select attribute).First(); + select attribute).FirstOrDefault(); + + if (renderingSubsystemAttribute == null) + { + throw new InvalidOperationException("No rendering subsystem found. Are you missing assembly references?"); + } UseWindowingSubsystem(() => windowingSubsystemAttribute.InitializationType .GetRuntimeMethod(windowingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null), From a16014f87559fb0ad64d8514358206f0a3f123c7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 21 Aug 2017 13:04:16 -0500 Subject: [PATCH 005/110] Upgrade Moq to reduce build warnings --- build/Moq.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Moq.props b/build/Moq.props index 55242d922e..7de9b6b6ba 100644 --- a/build/Moq.props +++ b/build/Moq.props @@ -1,5 +1,5 @@  - + From 58b3ff90ab99743912d5ee7ab7ce21070f9e5da4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 21 Aug 2017 14:19:34 -0500 Subject: [PATCH 006/110] Reduce build warnings by setting a few flags and not using deprecated overloads. --- build/UnitTests.NetCore.targets | 19 ----------- .../Avalonia.HtmlRenderer.csproj | 1 + .../NativeUnsafeMethods.cs | 1 + .../AppHost/HostedAppModel.cs | 1 - .../Interop/UnmanagedMethods.cs | 4 +-- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 6 ++-- .../Avalonia.Input.UnitTests.csproj | 32 ------------------- .../Avalonia.Styling.UnitTests.csproj | 1 + 9 files changed, 8 insertions(+), 59 deletions(-) diff --git a/build/UnitTests.NetCore.targets b/build/UnitTests.NetCore.targets index 13bb4ed230..42da8e4ab1 100644 --- a/build/UnitTests.NetCore.targets +++ b/build/UnitTests.NetCore.targets @@ -3,25 +3,6 @@ false true - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - MinimumRecommendedRules.ruleset - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - MinimumRecommendedRules.ruleset - diff --git a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj index f715217e42..e7822324a9 100644 --- a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj +++ b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj @@ -4,6 +4,7 @@ False False false + CS0436 true diff --git a/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs b/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs index ad8def369d..fbbf036b74 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs @@ -72,6 +72,7 @@ namespace Avalonia.LinuxFramebuffer FB_VBLANK_HAVE_VSYNC = 0x100 /* verical syncs can be detected */ } + [StructLayout(LayoutKind.Sequential)] unsafe struct fb_vblank { public VBlankFlags flags; /* FB_VBLANK flags */ __u32 count; /* counter of retraces since boot */ diff --git a/src/Windows/Avalonia.Designer/AppHost/HostedAppModel.cs b/src/Windows/Avalonia.Designer/AppHost/HostedAppModel.cs index a64304619a..b5d0687baa 100644 --- a/src/Windows/Avalonia.Designer/AppHost/HostedAppModel.cs +++ b/src/Windows/Avalonia.Designer/AppHost/HostedAppModel.cs @@ -86,7 +86,6 @@ namespace Avalonia.Designer.AppHost } double _currentScalingFactor = 1; - private string _color; private string _background; public event PropertyChangedEventHandler PropertyChanged; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index d5a6f1a7a1..5473ef9bea 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -922,9 +922,7 @@ namespace Avalonia.Win32.Interop [StructLayout(LayoutKind.Sequential)] internal class MONITORINFO { -#pragma warning disable CS0618 // Type or member is obsolete - public int cbSize = Marshal.SizeOf(typeof(MONITORINFO)); -#pragma warning restore CS0618 // Type or member is obsolete + public int cbSize = Marshal.SizeOf(); public RECT rcMonitor = new RECT(); public RECT rcWork = new RECT(); public int dwFlags = 0; diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 584a5ba39e..d8e9256156 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -167,7 +167,7 @@ namespace Avalonia.Win32 UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX { - cbSize = Marshal.SizeOf(typeof(UnmanagedMethods.WNDCLASSEX)), + cbSize = Marshal.SizeOf(), lpfnWndProc = _wndProcDelegate, hInstance = UnmanagedMethods.GetModuleHandle(null), lpszClassName = "AvaloniaMessageWindow " + Guid.NewGuid(), diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index feb7bdc2ee..4a30d48878 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -426,7 +426,7 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_DPICHANGED: var dpi = ToInt32(wParam) & 0xffff; - var newDisplayRect = (UnmanagedMethods.RECT)Marshal.PtrToStructure(lParam, typeof(UnmanagedMethods.RECT)); + var newDisplayRect = Marshal.PtrToStructure(lParam); Position = new Point(newDisplayRect.left, newDisplayRect.top); _scaling = dpi / 96.0; ScalingChanged?.Invoke(_scaling); @@ -494,7 +494,7 @@ namespace Avalonia.Win32 { var tm = new UnmanagedMethods.TRACKMOUSEEVENT { - cbSize = Marshal.SizeOf(typeof(UnmanagedMethods.TRACKMOUSEEVENT)), + cbSize = Marshal.SizeOf(), dwFlags = 2, hwndTrack = _hwnd, dwHoverTime = 0, @@ -619,7 +619,7 @@ namespace Avalonia.Win32 UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX { - cbSize = Marshal.SizeOf(typeof(UnmanagedMethods.WNDCLASSEX)), + cbSize = Marshal.SizeOf(), style = 0, lpfnWndProc = _wndProcDelegate, hInstance = UnmanagedMethods.GetModuleHandle(null), diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj index 186d293b96..710a818bcd 100644 --- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj +++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj @@ -3,35 +3,6 @@ net461;netcoreapp1.1 Library - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Input.UnitTests.XML - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - @@ -48,7 +19,4 @@ - - - \ No newline at end of file diff --git a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj index 8dd8faf9db..3f9213b91f 100644 --- a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj +++ b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj @@ -2,6 +2,7 @@ net461;netcoreapp1.1 Library + CS0067 From fec2823537e88bf79be32ca1d0be1348b63c03a5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 00:34:17 +0200 Subject: [PATCH 007/110] StyleResources -> ResourceDictionary. And move it to the `Avalonia.Controls` namespace. --- .../StyleResources.cs => Controls/ResourceDictionary.cs} | 6 +++--- src/Avalonia.Styling/Styling/Style.cs | 7 ++++--- tests/Avalonia.Styling.UnitTests/ResourceTests.cs | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) rename src/Avalonia.Styling/{Styling/StyleResources.cs => Controls/ResourceDictionary.cs} (95%) diff --git a/src/Avalonia.Styling/Styling/StyleResources.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs similarity index 95% rename from src/Avalonia.Styling/Styling/StyleResources.cs rename to src/Avalonia.Styling/Controls/ResourceDictionary.cs index e447c6adfd..96f523c83e 100644 --- a/src/Avalonia.Styling/Styling/StyleResources.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -2,12 +2,12 @@ using System.Collections; using System.Collections.Generic; -namespace Avalonia.Styling +namespace Avalonia.Controls { /// - /// Holds resources for a . + /// An indexed dictionary of resources. /// - public class StyleResources : IDictionary, IDictionary + public class ResourceDictionary : IDictionary, IDictionary { private Dictionary _inner = new Dictionary(); diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 3dfd9118af..d7df465722 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; +using Avalonia.Controls; using Avalonia.Metadata; namespace Avalonia.Styling @@ -16,7 +17,7 @@ namespace Avalonia.Styling private static Dictionary> _applied = new Dictionary>(); - private StyleResources _resources; + private ResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -37,13 +38,13 @@ namespace Avalonia.Styling /// /// Gets or sets a dictionary of style resources. /// - public StyleResources Resources + public ResourceDictionary Resources { get { if (_resources == null) { - _resources = new StyleResources(); + _resources = new ResourceDictionary(); } return _resources; diff --git a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs index a2535e0fb5..1efd043f6a 100644 --- a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs @@ -20,7 +20,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new StyleResources + Resources = new ResourceDictionary { { "Foo", "foo resource" }, { "Bar", "overridden" }, @@ -33,14 +33,14 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new StyleResources + Resources = new ResourceDictionary { { "Bar", "again overridden" }, } }, new Style { - Resources = new StyleResources + Resources = new ResourceDictionary { { "Bar", "bar resource" }, } @@ -64,7 +64,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new StyleResources + Resources = new ResourceDictionary { { "Foo", "foo" }, } From 39fc0ccdb5c23d2c973d05d307c2da0ef3b87d64 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 00:58:56 +0200 Subject: [PATCH 008/110] Added IResourceDictionary. --- .../Controls/IResourceDictionary.cs | 13 +++++++++++++ src/Avalonia.Styling/Controls/ResourceDictionary.cs | 2 +- src/Avalonia.Styling/Styling/Style.cs | 7 ++----- 3 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/IResourceDictionary.cs diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs new file mode 100644 index 0000000000..9891249568 --- /dev/null +++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Avalonia.Controls +{ + /// + /// An indexed dictionary of resources. + /// + public interface IResourceDictionary : IDictionary + { + } +} diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 96f523c83e..125bb0dcdf 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -7,7 +7,7 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public class ResourceDictionary : IDictionary, IDictionary + public class ResourceDictionary : IResourceDictionary, IDictionary { private Dictionary _inner = new Dictionary(); diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index d7df465722..637e583a25 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -17,7 +17,7 @@ namespace Avalonia.Styling private static Dictionary> _applied = new Dictionary>(); - private ResourceDictionary _resources; + private IResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -38,7 +38,7 @@ namespace Avalonia.Styling /// /// Gets or sets a dictionary of style resources. /// - public ResourceDictionary Resources + public IResourceDictionary Resources { get { @@ -52,15 +52,12 @@ namespace Avalonia.Styling set { - var resources = Resources; if (!Equals(resources, value)) { foreach (var i in value) { resources[i.Key] = i.Value; - //resources.Add(i.Key, i.Value); - //(resources as IDictionary).Add(i); } } } From b5df0cdcee6f0a7c6c56b767daf9dc60ed0dd82f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 01:03:02 +0200 Subject: [PATCH 009/110] Remove Style.Resources setter. We don't need it. --- src/Avalonia.Styling/Styling/Style.cs | 12 ------------ tests/Avalonia.Styling.UnitTests/ResourceTests.cs | 8 ++++---- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 637e583a25..80238889c8 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -49,18 +49,6 @@ namespace Avalonia.Styling return _resources; } - - set - { - var resources = Resources; - if (!Equals(resources, value)) - { - foreach (var i in value) - { - resources[i.Key] = i.Value; - } - } - } } /// diff --git a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs index 1efd043f6a..bfef1bd762 100644 --- a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs @@ -20,7 +20,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new ResourceDictionary + Resources = { { "Foo", "foo resource" }, { "Bar", "overridden" }, @@ -33,14 +33,14 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new ResourceDictionary + Resources = { { "Bar", "again overridden" }, } }, new Style { - Resources = new ResourceDictionary + Resources = { { "Bar", "bar resource" }, } @@ -64,7 +64,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new ResourceDictionary + Resources = { { "Foo", "foo" }, } From 84aa27162f33bc80d60a66ad4eca57a1435d2ad4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 02:59:41 +0200 Subject: [PATCH 010/110] Made a start adding Control/Application.Resources. --- samples/ControlCatalog/MainWindow.xaml.cs | 2 +- src/Avalonia.Controls/Application.cs | 19 +++- src/Avalonia.Controls/Control.cs | 15 ++++ src/Avalonia.Controls/ControlExtensions.cs | 29 +++++++ src/Avalonia.Controls/IControl.cs | 2 +- .../Presenters/TextPresenter.cs | 2 +- src/Avalonia.Styling/Avalonia.Styling.csproj | 1 + .../Controls/IResourceDictionary.cs | 17 +++- .../Controls/IResourceHost.cs | 22 +++++ .../Controls/ResourceDictionary.cs | 2 + src/Avalonia.Styling/Styling/IStyle.cs | 13 +-- src/Avalonia.Styling/Styling/IStyleHost.cs | 1 - src/Avalonia.Styling/Styling/Style.cs | 22 +---- .../Styling/StyleExtensions.cs | 39 --------- src/Avalonia.Styling/Styling/Styles.cs | 40 ++++++--- .../Data/StyleResourceBinding.cs | 7 +- .../Styling/StyleInclude.cs | 14 +-- .../ControlTests_Resources.cs | 87 +++++++++++++++++++ .../FullLayoutTests.cs | 4 + .../Xaml/BasicTests.cs | 6 +- .../Xaml/StyleTests.cs | 12 +-- .../ResourceTests.cs | 78 ----------------- tests/Avalonia.UnitTests/TestRoot.cs | 4 + 23 files changed, 249 insertions(+), 189 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/IResourceHost.cs delete mode 100644 src/Avalonia.Styling/Styling/StyleExtensions.cs create mode 100644 tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs delete mode 100644 tests/Avalonia.Styling.UnitTests/ResourceTests.cs diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 413794dfa2..0d24967482 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -19,7 +19,7 @@ namespace ControlCatalog // so we must refer to this resource DLL statically. For // now I am doing that here. But we need a better solution!! var theme = new Avalonia.Themes.Default.DefaultTheme(); - theme.FindResource("Button"); + theme.TryGetResource("Button"); AvaloniaXamlLoader.Load(this); } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 3d13608226..0d89ad65b1 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -29,7 +29,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IApplicationLifecycle + public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceHost { /// /// The application-global data templates. @@ -39,6 +39,7 @@ namespace Avalonia private readonly Lazy _clipboard = new Lazy(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))); private readonly Styler _styler = new Styler(); + private ResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -100,6 +101,11 @@ namespace Avalonia /// public IClipboard Clipboard => _clipboard.Value; + /// + /// Gets the application's global resource dictionary. + /// + public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary()); + /// /// Gets the application's global styles. /// @@ -142,13 +148,20 @@ namespace Avalonia { OnExit?.Invoke(this, EventArgs.Empty); } - + + /// + bool IResourceHost.TryGetResource(string key, out object value) + { + value = null; + return _resources?.TryGetResource(key, out value) ?? + Styles.TryGetResource(key, out value); + } + /// /// Sent when the application is exiting. /// public event EventHandler OnExit; - /// /// Called when the application is exiting. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index eca5967a58..ab5d9a7f06 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -97,6 +97,7 @@ namespace Avalonia.Controls private bool _isAttachedToLogicalTree; private IAvaloniaList _logicalChildren; private INameScope _nameScope; + private ResourceDictionary _resources; private Styles _styles; private bool _styled; private Subject _styleDetach = new Subject(); @@ -286,6 +287,11 @@ namespace Avalonia.Controls set { SetValue(ContextMenuProperty, value); } } + /// + /// Gets or sets the control's resource dictionary. + /// + public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary()); + /// /// Gets or sets a user-defined object attached to the control. /// @@ -418,6 +424,15 @@ namespace Avalonia.Controls /// protected IPseudoClasses PseudoClasses => Classes; + /// + bool IResourceHost.TryGetResource(string key, out object value) + { + value = null; + return _resources?.TryGetResource(key, out value) ?? + _styles?.TryGetResource(key, out value) ?? + false; + } + /// /// Sets the control's logical parent. /// diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index 60a940627f..d0b8b75e7b 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -81,6 +81,35 @@ namespace Avalonia.Controls .FirstOrDefault(x => x != null); } + /// + /// Finds the specified resource by searching up the logical tree and then global styles. + /// + /// The control. + /// The resource key. + /// The resource, or null if not found. + public static object FindResource(this IControl control, string key) + { + Contract.Requires(control != null); + Contract.Requires(key != null); + + var current = control as IStyleHost; + + while (current != null) + { + if (current is IResourceHost host) + { + if (host.TryGetResource(key, out var value)) + { + return value; + } + } + + current = current.StylingParent; + } + + return null; + } + /// /// Adds or removes a pseudoclass depending on a boolean value. /// diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index 3f5bd3fcac..02973bb38d 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// /// Interface for Avalonia controls. /// - public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IStyleable, IStyleHost + public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IResourceHost, IStyleable, IStyleHost { /// /// Occurs when the control has finished initialization. diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index a2b5f3f8b4..5f6b3ad4c8 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -115,7 +115,7 @@ namespace Avalonia.Controls.Presenters if (_highlightBrush == null) { - _highlightBrush = (IBrush)this.FindStyleResource("HighlightBrush"); + _highlightBrush = (IBrush)this.FindResource("HighlightBrush"); } foreach (var rect in rects) diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj index 6bf37b522b..6b606616bb 100644 --- a/src/Avalonia.Styling/Avalonia.Styling.csproj +++ b/src/Avalonia.Styling/Avalonia.Styling.csproj @@ -2,6 +2,7 @@ netstandard1.3 false + Avalonia true diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs index 9891249568..bc76d8f60e 100644 --- a/src/Avalonia.Styling/Controls/IResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs @@ -1,5 +1,7 @@ -using System; -using System.Collections; +// 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.Collections.Generic; namespace Avalonia.Controls @@ -9,5 +11,16 @@ namespace Avalonia.Controls /// public interface IResourceDictionary : IDictionary { + /// + /// Tries to find a resource within the dictionary. + /// + /// The resource key. + /// + /// When this method returns, contains the value associated with the specified key, + /// if the key is found; otherwise, null + /// + /// True if the resource if found, otherwise false. + /// + bool TryGetResource(string key, out object value); } } diff --git a/src/Avalonia.Styling/Controls/IResourceHost.cs b/src/Avalonia.Styling/Controls/IResourceHost.cs new file mode 100644 index 0000000000..6cee7083e0 --- /dev/null +++ b/src/Avalonia.Styling/Controls/IResourceHost.cs @@ -0,0 +1,22 @@ +using System; + +namespace Avalonia.Controls +{ + /// + /// Defines an element that can be queried for resources. + /// + public interface IResourceHost + { + /// + /// Tries to find a resource within the element. + /// + /// The resource key. + /// + /// When this method returns, contains the value associated with the specified key, + /// if the key is found; otherwise, null + /// + /// True if the resource if found, otherwise false. + /// + bool TryGetResource(string key, out object value); + } +} diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 125bb0dcdf..58dee17775 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -55,6 +55,8 @@ namespace Avalonia.Controls public bool TryGetValue(string key, out object value) => _inner.TryGetValue(key, out value); + public bool TryGetResource(string key, out object value) => _inner.TryGetValue(key, out value); + bool ICollection>.Contains(KeyValuePair item) { return ((IDictionary)_inner).Contains(item); diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs index 7b8510fe2d..70f6c60d14 100644 --- a/src/Avalonia.Styling/Styling/IStyle.cs +++ b/src/Avalonia.Styling/Styling/IStyle.cs @@ -1,12 +1,14 @@ // 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.Controls; + namespace Avalonia.Styling { /// /// Defines the interface for styles. /// - public interface IStyle + public interface IStyle : IResourceHost { /// /// Attaches the style to a control if the style's selector matches. @@ -16,14 +18,5 @@ namespace Avalonia.Styling /// The control that contains this style. May be null. /// void Attach(IStyleable control, IStyleHost container); - - /// - /// Tries to find a named resource within the style. - /// - /// The resource name. - /// - /// The resource if found, otherwise . - /// - object FindResource(string name); } } diff --git a/src/Avalonia.Styling/Styling/IStyleHost.cs b/src/Avalonia.Styling/Styling/IStyleHost.cs index 8422f18b46..466edc4423 100644 --- a/src/Avalonia.Styling/Styling/IStyleHost.cs +++ b/src/Avalonia.Styling/Styling/IStyleHost.cs @@ -17,6 +17,5 @@ namespace Avalonia.Styling /// Gets the parent style host element. /// IStyleHost StylingParent { get; } - } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 80238889c8..5dc0409cf0 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -98,25 +98,11 @@ namespace Avalonia.Styling } } - /// - /// Tries to find a named resource within the style. - /// - /// The resource name. - /// - /// The resource if found, otherwise . - /// - public object FindResource(string name) + /// + public bool TryGetResource(string key, out object result) { - object result = null; - - if (_resources?.TryGetValue(name, out result) == true) - { - return result; - } - else - { - return AvaloniaProperty.UnsetValue; - } + result = null; + return _resources?.TryGetResource(key, out result) ?? false; } /// diff --git a/src/Avalonia.Styling/Styling/StyleExtensions.cs b/src/Avalonia.Styling/Styling/StyleExtensions.cs deleted file mode 100644 index e1073335a0..0000000000 --- a/src/Avalonia.Styling/Styling/StyleExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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; - -namespace Avalonia.Styling -{ - public static class StyleExtensions - { - /// - /// Tries to find a named style resource. - /// - /// The control from which to find the resource. - /// The resource name. - /// - /// The resource if found, otherwise . - /// - public static object FindStyleResource(this IStyleHost control, string name) - { - Contract.Requires(control != null); - Contract.Requires(name != null); - Contract.Requires(!string.IsNullOrWhiteSpace(name)); - - while (control != null) - { - var result = control.Styles.FindResource(name); - - if (result != AvaloniaProperty.UnsetValue) - { - return result; - } - - control = control.StylingParent; - } - - return AvaloniaProperty.UnsetValue; - } - } -} diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 770ef8344f..75b8fd0c0c 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -3,6 +3,7 @@ using System.Linq; using Avalonia.Collections; +using Avalonia.Controls; namespace Avalonia.Styling { @@ -11,6 +12,24 @@ namespace Avalonia.Styling /// public class Styles : AvaloniaList, IStyle { + private IResourceDictionary _resources; + + /// + /// Gets or sets a dictionary of style resources. + /// + public IResourceDictionary Resources + { + get + { + if (_resources == null) + { + _resources = new ResourceDictionary(); + } + + return _resources; + } + } + /// /// Attaches the style to a control if the style's selector matches. /// @@ -26,26 +45,19 @@ namespace Avalonia.Styling } } - /// - /// Tries to find a named resource within the style. - /// - /// The resource name. - /// - /// The resource if found, otherwise . - /// - public object FindResource(string name) + /// + public bool TryGetResource(string key, out object value) { - foreach (var style in this.Reverse()) + for (var i = Count - 1; i >= 0; --i) { - var result = style.FindResource(name); - - if (result != AvaloniaProperty.UnsetValue) + if (this[i].TryGetResource(key, out value)) { - return result; + return true; } } - return AvaloniaProperty.UnsetValue; + value = null; + return false; } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs index c1a895f797..787aebbdc6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs @@ -46,11 +46,14 @@ namespace Avalonia.Markup.Xaml.Data if (host != null) { - resource = host.FindStyleResource(Name); + resource = host.FindResource(Name); } else if (style != null) { - resource = style.FindResource(Name); + if (!style.TryGetResource(Name, out resource)) + { + resource = AvaloniaProperty.UnsetValue; + } } if (resource != AvaloniaProperty.UnsetValue) diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 0f4824d493..a8fecdc6e1 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -3,6 +3,7 @@ using Avalonia.Styling; using System; +using Avalonia.Controls; namespace Avalonia.Markup.Xaml.Styling { @@ -55,16 +56,7 @@ namespace Avalonia.Markup.Xaml.Styling } } - /// - /// Tries to find a named resource within the style. - /// - /// The resource name. - /// - /// The resource if found, otherwise . - /// - public object FindResource(string name) - { - return Loaded.FindResource(name); - } + /// + public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value); } } \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs new file mode 100644 index 0000000000..361806c0a8 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs @@ -0,0 +1,87 @@ +// 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 Avalonia.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class ControlTests_Resources + { + [Fact] + public void FindResource_Should_Find_Control_Resource() + { + var target = new Control + { + Resources = + { + { "foo", "foo-value" }, + } + }; + + Assert.Equal("foo-value", target.FindResource("foo")); + } + + [Fact] + public void FindResource_Should_Find_Control_Resource_In_Parent() + { + Control target; + + var root = new Decorator + { + Resources = + { + { "foo", "foo-value" }, + }, + Child = target = new Control(), + }; + + Assert.Equal("foo-value", target.FindResource("foo")); + } + + [Fact] + public void FindResource_Should_Find_Application_Resource() + { + Control target; + + var app = new Application + { + Resources = + { + { "foo", "foo-value" }, + }, + }; + + var root = new TestRoot + { + Child = target = new Control(), + StylingParent = app, + }; + + Assert.Equal("foo-value", target.FindResource("foo")); + } + + [Fact] + public void FindResource_Should_Find_Style_Resource() + { + var target = new Control + { + Styles = + { + new Style + { + Resources = + { + { "foo", "foo-value" }, + } + } + } + }; + + Assert.Equal("foo-value", target.FindResource("foo")); + } + + } +} diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index 6b7c73da2a..006306a3e4 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -162,6 +162,10 @@ namespace Avalonia.Layout.UnitTests private void RegisterServices() { var globalStyles = new Mock(); + var globalStylesResources = globalStyles.As(); + var outObj = (object)10; + globalStylesResources.Setup(x => x.TryGetResource("FontSizeNormal", out outObj)).Returns(true); + var renderInterface = new Mock(); renderInterface.Setup(x => x.CreateFormattedText( diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 318a98ad43..ce370790c3 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -480,13 +480,13 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.True(style.Resources.Count > 0); - var brush = style.FindResource("Brush") as SolidColorBrush; + style.TryGetResource("Brush", out var brush); Assert.NotNull(brush); - Assert.Equal(Colors.White, brush.Color); + Assert.Equal(Colors.White, ((SolidColorBrush)brush).Color); - var d = (double)style.FindResource("Double"); + style.TryGetResource("Double", out var d); Assert.Equal(10.0, d); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index 0c5a89b827..65fc9eaddd 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -141,7 +141,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var loader = new AvaloniaXamlLoader(); var window = (Window)loader.Load(xaml); - var brush = (ISolidColorBrush)window.FindStyleResource("brush"); + var brush = (ISolidColorBrush)window.FindResource("brush"); var button = window.FindControl IStyleHost IStyleHost.StylingParent => null; + /// + bool IResourceProvider.HasResources => _resources?.Count > 0; + /// /// Initializes the application by loading XAML etc. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 81acf1a204..c93742b7f6 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -382,6 +382,9 @@ namespace Avalonia.Controls /// IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren; + /// + bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; + /// IAvaloniaReadOnlyList IStyleable.Classes => Classes; diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index 9308b7a18d..e3ea2e1aab 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -12,6 +12,11 @@ namespace Avalonia.Controls /// event EventHandler ResourcesChanged; + /// + /// Gets a value indicating whether the provider has resources. + /// + bool HasResources { get; } + /// /// Tries to find a resource within the element. /// diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 1ea99a42ac..b095f38035 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -66,6 +66,9 @@ namespace Avalonia.Styling [Content] public IList Setters { get; set; } = new List(); + /// + bool IResourceProvider.HasResources => _resources?.Count > 0; + /// /// Attaches the style to a control if the style's selector matches. /// diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 4ce41f6f46..47647d726e 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -20,14 +20,33 @@ namespace Avalonia.Styling { ResetBehavior = ResetBehavior.Remove; this.ForEachItem( - x => x.ResourcesChanged += SubResourceChanged, - x => x.ResourcesChanged -= SubResourceChanged, + x => + { + if (x.HasResources) + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + + x.ResourcesChanged += SubResourceChanged; + }, + x => + { + if (x.HasResources) + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + + x.ResourcesChanged -= SubResourceChanged; + }, () => { }); } /// public event EventHandler ResourcesChanged; + /// + public bool HasResources => _resources?.Count > 0 || this.Any(x => x.HasResources); + /// /// Gets or sets a dictionary of style resources. /// diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 556e2324cd..8e571af4a0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -50,6 +50,9 @@ namespace Avalonia.Markup.Xaml.Styling } } + /// + bool IResourceProvider.HasResources => Loaded.HasResources; + /// public void Attach(IStyleable control, IStyleHost container) { diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs index 4fa00ab171..561cad87c7 100644 --- a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs +++ b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -142,5 +144,112 @@ namespace Avalonia.Controls.UnitTests Assert.Equal("foo-value", target.FindResource("foo")); } + + [Fact] + public void Adding_Resource_Should_Call_Raise_ResourceChanged_On_Logical_Children() + { + Border child; + + var target = new ContentControl + { + Content = child = new Border(), + Template = ContentControlTemplate(), + }; + + var raisedOnTarget = false; + var raisedOnPresenter = false; + var raisedOnChild = false; + + target.Measure(Size.Infinity); + target.ResourcesChanged += (_, __) => raisedOnTarget = true; + target.Presenter.ResourcesChanged += (_, __) => raisedOnPresenter = true; + child.ResourcesChanged += (_, __) => raisedOnChild = true; + + target.Resources.Add("foo", "bar"); + + Assert.True(raisedOnTarget); + Assert.False(raisedOnPresenter); + Assert.True(raisedOnChild); + } + + [Fact] + public void Adding_Resource_To_Styles_Should_Raise_ResourceChanged() + { + var target = new Decorator(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Styles.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void Adding_Resource_To_Nested_Style_Should_Raise_ResourceChanged() + { + Style style; + var target = new Decorator + { + Styles = + { + (style = new Style()), + } + }; + + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + style.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void Adding_Style_With_Resource_Should_Raise_ResourceChanged() + { + Style style = new Style + { + Resources = { { "foo", "bar" } }, + }; + + var target = new Decorator(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Styles.Add(style); + + Assert.True(raised); + } + + [Fact] + public void Removing_Style_With_Resource_Should_Raise_ResourceChanged() + { + var target = new Decorator + { + Styles = + { + new Style + { + Resources = { { "foo", "bar" } }, + } + } + }; + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Styles.Clear(); + + Assert.True(raised); + } + + private IControlTemplate ContentControlTemplate() + { + return new FuncControlTemplate(x => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], + }); + } } } From 0e155bd2d4925800ad6fefe32cb4d6e891ba4d4a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 02:50:49 +0200 Subject: [PATCH 033/110] Make dynamic resources work in more situations. Fixes #492 in a fashion: `DynamicResource` now works for this scenario. --- src/Avalonia.Controls/Application.cs | 3 + src/Avalonia.Controls/Control.cs | 10 ++ src/Avalonia.Controls/ControlExtensions.cs | 29 ----- .../Controls/IResourceProvider.cs | 5 + .../Controls/ResourceProviderExtensions.cs | 49 ++++++++ src/Avalonia.Styling/LogicalTree/ILogical.cs | 2 +- .../Styling/ISetStyleParent.cs | 32 +++++ src/Avalonia.Styling/Styling/Style.cs | 23 +++- src/Avalonia.Styling/Styling/Styles.cs | 56 ++++++++- .../DynamicResourceExtension.cs | 16 +-- .../Styling/StyleInclude.cs | 25 +++- .../ControlTests_Resources.cs | 38 ------ .../DynamicResourceExtensionTests.cs | 4 - .../SelectorTests_Child.cs | 5 + .../SelectorTests_Descendent.cs | 5 + .../Avalonia.Styling.UnitTests/StylesTests.cs | 115 ++++++++++++++++++ 16 files changed, 329 insertions(+), 88 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs create mode 100644 src/Avalonia.Styling/Styling/ISetStyleParent.cs create mode 100644 tests/Avalonia.Styling.UnitTests/StylesTests.cs diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 4533855b73..ce15c0b9e3 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -128,6 +128,9 @@ namespace Avalonia /// bool IResourceProvider.HasResources => _resources?.Count > 0; + /// + IResourceProvider IResourceProvider.ResourceParent => null; + /// /// Initializes the application by loading XAML etc. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index c93742b7f6..a40b59d60a 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -283,10 +283,17 @@ namespace Avalonia.Controls { if (_styles != null) { + (_styles as ISetStyleParent)?.SetParent(null); _styles.ResourcesChanged -= StyleResourcesChanged; } _styles = value; + + if (value is ISetStyleParent setParent && setParent.ResourceParent == null) + { + setParent.SetParent(this); + } + _styles.ResourcesChanged += StyleResourcesChanged; } } @@ -385,6 +392,9 @@ namespace Avalonia.Controls /// bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; + /// + IResourceProvider IResourceProvider.ResourceParent => ((IStyleHost)this).StylingParent as IResourceProvider; + /// IAvaloniaReadOnlyList IStyleable.Classes => Classes; diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index 87c774dfcf..60a940627f 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -81,35 +81,6 @@ namespace Avalonia.Controls .FirstOrDefault(x => x != null); } - /// - /// Finds the specified resource by searching up the logical tree and then global styles. - /// - /// The control. - /// The resource key. - /// The resource, or if not found. - public static object FindResource(this IControl control, string key) - { - Contract.Requires(control != null); - Contract.Requires(key != null); - - var current = control as IStyleHost; - - while (current != null) - { - if (current is IResourceProvider host) - { - if (host.TryGetResource(key, out var value)) - { - return value; - } - } - - current = current.StylingParent; - } - - return AvaloniaProperty.UnsetValue; - } - /// /// Adds or removes a pseudoclass depending on a boolean value. /// diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index e3ea2e1aab..180476b2e4 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -17,6 +17,11 @@ namespace Avalonia.Controls /// bool HasResources { get; } + /// + /// Gets the parent resource provider, if any. + /// + IResourceProvider ResourceParent { get; } + /// /// Tries to find a resource within the element. /// diff --git a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs new file mode 100644 index 0000000000..45e16438d0 --- /dev/null +++ b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Reactive.Linq; +using System.Text; + +namespace Avalonia.Controls +{ + public static class ResourceProviderExtensions + { + /// + /// Finds the specified resource by searching up the logical tree and then global styles. + /// + /// The control. + /// The resource key. + /// The resource, or if not found. + public static object FindResource(this IResourceProvider control, string key) + { + Contract.Requires(control != null); + Contract.Requires(key != null); + + var current = control; + + while (current != null) + { + if (current is IResourceProvider host) + { + if (host.TryGetResource(key, out var value)) + { + return value; + } + } + + current = current.ResourceParent; + } + + return AvaloniaProperty.UnsetValue; + } + + public static IObservable GetResourceObservable(this IResourceProvider target, string key) + { + return Observable.FromEventPattern( + x => target.ResourcesChanged += x, + x => target.ResourcesChanged -= x) + .StartWith((EventPattern)null) + .Select(x => target.FindResource(key)); + } + } +} diff --git a/src/Avalonia.Styling/LogicalTree/ILogical.cs b/src/Avalonia.Styling/LogicalTree/ILogical.cs index a6e804567d..8ee3c9ea4f 100644 --- a/src/Avalonia.Styling/LogicalTree/ILogical.cs +++ b/src/Avalonia.Styling/LogicalTree/ILogical.cs @@ -58,7 +58,7 @@ namespace Avalonia.LogicalTree void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e); /// - /// Notifies the control that a change has been made to its resources. + /// Notifies the control that a change has been made to resources that apply to it. /// /// The event args. /// diff --git a/src/Avalonia.Styling/Styling/ISetStyleParent.cs b/src/Avalonia.Styling/Styling/ISetStyleParent.cs new file mode 100644 index 0000000000..da5b34798e --- /dev/null +++ b/src/Avalonia.Styling/Styling/ISetStyleParent.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls; + +namespace Avalonia.Styling +{ + /// + /// Defines an interface through which a 's parent can be set. + /// + /// + /// You should not usually need to use this interface - it is for internal use only. + /// + public interface ISetStyleParent : IStyle + { + /// + /// Sets the style parent. + /// + /// The parent. + void SetParent(IResourceProvider parent); + + /// + /// Notifies the style that a change has been made to resources that apply to it. + /// + /// The event args. + /// + /// This method will be called automatically by the framework, you should not need to call + /// this method yourself. + /// + void NotifyResourcesChanged(ResourcesChangedEventArgs e); + } +} diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index b095f38035..54b53868da 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -13,10 +13,11 @@ namespace Avalonia.Styling /// /// Defines a style. /// - public class Style : IStyle + public class Style : IStyle, ISetStyleParent { private static Dictionary> _applied = new Dictionary>(); + private IResourceProvider _parent; private ResourceDictionary _resources; /// @@ -69,6 +70,9 @@ namespace Avalonia.Styling /// bool IResourceProvider.HasResources => _resources?.Count > 0; + /// + IResourceProvider IResourceProvider.ResourceParent => _parent; + /// /// Attaches the style to a control if the style's selector matches. /// @@ -128,6 +132,23 @@ namespace Avalonia.Styling } } + /// + void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + ResourcesChanged?.Invoke(this, e); + } + + /// + void ISetStyleParent.SetParent(IResourceProvider parent) + { + if (_parent != null && parent != null) + { + throw new InvalidOperationException("The Style already has a parent."); + } + + _parent = parent; + } + private static List GetSubscriptions(IStyleable control) { List subscriptions; diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 47647d726e..43a542d460 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -12,8 +12,9 @@ namespace Avalonia.Styling /// /// A style that consists of a number of child styles. /// - public class Styles : AvaloniaList, IStyle + public class Styles : AvaloniaList, IStyle, ISetStyleParent { + private IResourceProvider _parent; private ResourceDictionary _resources; public Styles() @@ -22,6 +23,12 @@ namespace Avalonia.Styling this.ForEachItem( x => { + if (x.ResourceParent == null && x is ISetStyleParent setParent) + { + setParent.SetParent(this); + setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } + if (x.HasResources) { ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); @@ -31,6 +38,12 @@ namespace Avalonia.Styling }, x => { + if (x.ResourceParent == this && x is ISetStyleParent setParent) + { + setParent.SetParent(null); + setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } + if (x.HasResources) { ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); @@ -64,6 +77,9 @@ namespace Avalonia.Styling } } + /// + IResourceProvider IResourceProvider.ResourceParent => _parent; + /// /// Attaches the style to a control if the style's selector matches. /// @@ -99,13 +115,49 @@ namespace Avalonia.Styling return false; } + /// + void ISetStyleParent.SetParent(IResourceProvider parent) + { + if (_parent != null && parent != null) + { + throw new InvalidOperationException("The Style already has a parent."); + } + + _parent = parent; + } + + /// + void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + ResourcesChanged?.Invoke(this, e); + } + private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e) { - ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + var ev = new ResourcesChangedEventArgs(); + + foreach (var child in this) + { + (child as ISetStyleParent)?.NotifyResourcesChanged(ev); + } + + ResourcesChanged?.Invoke(this, ev); } private void SubResourceChanged(object sender, ResourcesChangedEventArgs e) { + var foundSource = false; + + foreach (var child in this) + { + if (foundSource) + { + (child as ISetStyleParent)?.NotifyResourcesChanged(e); + } + + foundSource |= child == sender; + } + ResourcesChanged?.Invoke(this, e); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 7921510944..905bf16d1f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -15,7 +15,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { public class DynamicResourceExtension : MarkupExtension, IBinding { - private IControl _anchor; + private IResourceProvider _anchor; public DynamicResourceExtension() { @@ -33,9 +33,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var context = (ITypeDescriptorContext)serviceProvider; var provideTarget = context.GetService(); - if (!(provideTarget.TargetObject is IControl)) + if (!(provideTarget.TargetObject is IResourceProvider)) { - _anchor = GetAnchor(context); + _anchor = GetAnchor(context); } return this; @@ -47,17 +47,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions object anchor, bool enableDataValidation) { - var control = target as IControl ?? _anchor as IControl; + var control = target as IResourceProvider ?? _anchor; if (control != null) { - var o = Observable.FromEventPattern( - x => control.ResourcesChanged += x, - x => control.ResourcesChanged -= x) - .StartWith((EventPattern)null) - .Select(x => control.FindResource(ResourceKey)); - - return new InstancedBinding(o); + return new InstancedBinding(control.GetResourceObservable(ResourceKey)); } return null; diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 8e571af4a0..a121cd8c1b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -10,16 +10,16 @@ namespace Avalonia.Markup.Xaml.Styling /// /// Includes a style from a URL. /// - public class StyleInclude : IStyle + public class StyleInclude : IStyle, ISetStyleParent { private Uri _baseUri; private IStyle _loaded; + private IResourceProvider _parent; /// /// Initializes a new instance of the class. /// /// - public StyleInclude(Uri baseUri) { _baseUri = baseUri; @@ -44,6 +44,7 @@ namespace Avalonia.Markup.Xaml.Styling { var loader = new AvaloniaXamlLoader(); _loaded = (IStyle)loader.Load(Source, _baseUri); + (_loaded as ISetStyleParent)?.SetParent(this); } return _loaded; @@ -53,6 +54,9 @@ namespace Avalonia.Markup.Xaml.Styling /// bool IResourceProvider.HasResources => Loaded.HasResources; + /// + IResourceProvider IResourceProvider.ResourceParent => _parent; + /// public void Attach(IStyleable control, IStyleHost container) { @@ -64,5 +68,22 @@ namespace Avalonia.Markup.Xaml.Styling /// public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value); + + /// + void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + (Loaded as ISetStyleParent)?.NotifyResourcesChanged(e); + } + + /// + void ISetStyleParent.SetParent(IResourceProvider parent) + { + if (_parent != null && parent != null) + { + throw new InvalidOperationException("The Style already has a parent."); + } + + _parent = parent; + } } } \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs index 561cad87c7..6a6cb48001 100644 --- a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs +++ b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs @@ -204,44 +204,6 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } - [Fact] - public void Adding_Style_With_Resource_Should_Raise_ResourceChanged() - { - Style style = new Style - { - Resources = { { "foo", "bar" } }, - }; - - var target = new Decorator(); - var raised = false; - - target.ResourcesChanged += (_, __) => raised = true; - target.Styles.Add(style); - - Assert.True(raised); - } - - [Fact] - public void Removing_Style_With_Resource_Should_Raise_ResourceChanged() - { - var target = new Decorator - { - Styles = - { - new Style - { - Resources = { { "foo", "bar" } }, - } - } - }; - var raised = false; - - target.ResourcesChanged += (_, __) => raised = true; - target.Styles.Clear(); - - Assert.True(raised); - } - private IControlTemplate ContentControlTemplate() { return new FuncControlTemplate(x => diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 46a90635b2..a8e93b29f0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -360,8 +360,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'> Red - Green - Blue "; var style2Xaml = @" @@ -369,8 +367,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'> - - "; using (StyledWindow( diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs index 50b4828e73..352efe5358 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs @@ -154,6 +154,11 @@ namespace Avalonia.Styling.UnitTests { throw new NotImplementedException(); } + + public void NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + throw new NotImplementedException(); + } } public class TestLogical1 : TestLogical diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs index 7cf8c3dd1c..c413904c8f 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs @@ -184,6 +184,11 @@ namespace Avalonia.Styling.UnitTests { throw new NotImplementedException(); } + + public void NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + throw new NotImplementedException(); + } } public class TestLogical1 : TestLogical diff --git a/tests/Avalonia.Styling.UnitTests/StylesTests.cs b/tests/Avalonia.Styling.UnitTests/StylesTests.cs new file mode 100644 index 0000000000..c033dad0c6 --- /dev/null +++ b/tests/Avalonia.Styling.UnitTests/StylesTests.cs @@ -0,0 +1,115 @@ +// 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 Xunit; + +namespace Avalonia.Styling.UnitTests +{ + public class StylesTests + { + [Fact] + public void Adding_Style_With_Resources_Should_Raise_ResourceChanged() + { + var style = new Style + { + Resources = { { "foo", "bar" } }, + }; + + var target = new Styles(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Add(style); + + Assert.True(raised); + } + + [Fact] + public void Removing_Style_With_Resources_Should_Raise_ResourceChanged() + { + var target = new Styles + { + new Style + { + Resources = { { "foo", "bar" } }, + } + }; + + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Clear(); + + Assert.True(raised); + } + + [Fact] + public void Adding_Style_Without_Resources_Should_Not_Raise_ResourceChanged() + { + var style = new Style(); + var target = new Styles(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Add(style); + + Assert.False(raised); + } + + [Fact] + public void Adding_Resource_Should_Raise_Child_ResourceChanged() + { + Style child; + var target = new Styles + { + (child = new Style()), + }; + + var raised = false; + + child.ResourcesChanged += (_, __) => raised = true; + target.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void Adding_Resource_To_Younger_Sibling_Style_Should_Raise_ResourceChanged() + { + Style style1; + Style style2; + var target = new Styles + { + (style1 = new Style()), + (style2 = new Style()), + }; + + var raised = false; + + style2.ResourcesChanged += (_, __) => raised = true; + style1.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void Adding_Resource_To_Older_Sibling_Style_Should_Raise_ResourceChanged() + { + Style style1; + Style style2; + var target = new Styles + { + (style1 = new Style()), + (style2 = new Style()), + }; + + var raised = false; + + style1.ResourcesChanged += (_, __) => raised = true; + style2.Resources.Add("foo", "bar"); + + Assert.False(raised); + } + } +} From 71e8b4fc69415c2aa3a90dccd3fe225e2a66fc2b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 11:21:57 +0200 Subject: [PATCH 034/110] Update dynamic resources on set parent. --- src/Avalonia.Controls/Control.cs | 1 + .../DynamicResourceExtensionTests.cs | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index a40b59d60a..c9a3b2940d 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -521,6 +521,7 @@ namespace Avalonia.Controls } _parent = (IControl)parent; + ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot) { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index a8e93b29f0..1a036546b4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -393,6 +393,38 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Control_Property_Is_Updated_When_Parent_Is_Changed() + { + var xaml = @" + + + #ff506070 + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + + userControl.Content = null; + + Assert.Null(border.Background); + + userControl.Content = border; + + brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From a0bda15576424eaa9b0b649685c7bdd50d028f4e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 11:30:27 +0200 Subject: [PATCH 035/110] More resource extension tests. One failing. --- .../DynamicResourceExtensionTests.cs | 22 +++++++++ .../StaticResourceExtensionTests.cs | 47 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 1a036546b4..f9c78eb80c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -40,6 +40,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + [Fact] + public void DynamicResource_Can_Be_Assigned_To_Attached_Property() + { + var xaml = @" + + + 5 + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + Assert.Equal(5, Grid.GetColumn(border)); + } + [Fact] public void DynamicResource_From_Style_Can_Be_Assigned_To_Property() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index cccbf4d0b5..fc30b0dd6d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -37,6 +37,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + + [Fact] + public void StaticResource_Can_Be_Assigned_To_Attached_Property() + { + var xaml = @" + + + 5 + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + Assert.Equal(5, Grid.GetColumn(border)); + } + [Fact] public void StaticResource_From_Style_Can_Be_Assigned_To_Property() { @@ -290,6 +311,32 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() + { + var xaml = @" + + + #ff506070 + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + + userControl.Content = null; + + brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From 9a74e8409ea1c5305c4b5ea481dfc4b359e6159a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 13:19:23 +0200 Subject: [PATCH 036/110] Ensure properties registered. When calling `AvaloniaPropertyRegistry.GetAttached`. --- src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 190b3ee2be..ec1643427b 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -47,6 +47,9 @@ namespace Avalonia { Dictionary inner; + // Ensure the type's static ctor has been run. + RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle); + if (_attached.TryGetValue(ownerType, out inner)) { return inner.Values; From fcee846228d4c0d957a24913cf3ecdb52935ff02 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 19:27:59 +0200 Subject: [PATCH 037/110] Added tests for #1020 Passing. Closes #1020. --- .../DynamicResourceExtensionTests.cs | 25 +++++++++++++++++++ .../StaticResourceExtensionTests.cs | 23 ++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index f9c78eb80c..7de60a1029 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -298,6 +298,31 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + + [Fact] + public void DynamicResource_Can_Be_Assigned_To_ItemTemplate_Property() + { + var xaml = @" + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var listBox = userControl.FindControl("listBox"); + + DelayedBinding.ApplyBindings(listBox); + + Assert.NotNull(listBox.ItemTemplate); + } + [Fact] public void DynamicResource_Tracks_Added_Resource() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index fc30b0dd6d..4f14f1eba8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -37,7 +37,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } - [Fact] public void StaticResource_Can_Be_Assigned_To_Attached_Property() { @@ -311,6 +310,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void StaticResource_Can_Be_Assigned_To_ItemTemplate_Property() + { + var xaml = @" + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var listBox = userControl.FindControl("listBox"); + + Assert.NotNull(listBox.ItemTemplate); + } + [Fact] public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() { From 2c1efe3773b95a7c7ec59b97c4e75192a96b72dc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 22:57:04 +0200 Subject: [PATCH 038/110] Static resource as binding converter Enable using a `StaticResource` as a binding converter. Fixes #818. --- .../DynamicResourceExtension.cs | 7 +++-- .../StaticResourceExtension.cs | 27 +++++++++++-------- .../StaticResourceExtensionTests.cs | 27 +++++++++++++++++++ .../MarkupExtensions/TestValueConverter.cs | 20 ++++++++++++++ 4 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 905bf16d1f..7937be60aa 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -3,7 +3,7 @@ using System; using System.ComponentModel; -using System.Reactive; +using System.Linq; using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Data; @@ -62,7 +62,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var schemaContext = context.GetService()?.SchemaContext; var ambientProvider = context.GetService(); var xamlType = schemaContext.GetXamlType(typeof(T)); - return ambientProvider.GetFirstAmbientValue(xamlType) as T; + + // We override XamlType.CanAssignTo in BindingXamlType so the results we get back + // from GetAllAmbientValues aren't necessarily of the correct type. + return ambientProvider.GetAllAmbientValues(xamlType).OfType().FirstOrDefault(); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index f92eca25bf..e3cd7b062b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -32,7 +32,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var schemaContext = context.GetService()?.SchemaContext; var ambientProvider = context.GetService(); var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceProvider)); - var resourceProviders = ambientProvider.GetAllAmbientValues(resourceProviderType); + var ambientValues = ambientProvider.GetAllAmbientValues(resourceProviderType); // Look upwards though the ambient context for IResourceProviders which might be able // to give us the resource. @@ -43,18 +43,23 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions // // StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File // - foreach (IResourceProvider resourceProvider in resourceProviders) + foreach (var ambientValue in ambientValues) { - if (resourceProvider is IControl control && control.StylingParent != null) + // We override XamlType.CanAssignTo in BindingXamlType so the results we get back + // from GetAllAmbientValues aren't necessarily of the correct type. + if (ambientValue is IResourceProvider resourceProvider) { - // If we've got to a control that has a StylingParent then it's probably - // a top level control and its StylingParent is pointing to the global - // styles. If this is case just do a FindResource on it. - return control.FindResource(ResourceKey); - } - else if (resourceProvider.TryGetResource(ResourceKey, out var value)) - { - return value; + if (resourceProvider is IControl control && control.StylingParent != null) + { + // If we've got to a control that has a StylingParent then it's probably + // a top level control and its StylingParent is pointing to the global + // styles. If this is case just do a FindResource on it. + return control.FindResource(ResourceKey); + } + else if (resourceProvider.TryGetResource(ResourceKey, out var value)) + { + return value; + } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index 4f14f1eba8..ed3ab3cc52 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -332,6 +332,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.NotNull(listBox.ItemTemplate); } + [Fact] + public void StaticResource_Can_Be_Assigned_To_Converter() + { + using (StyledWindow()) + { + var xaml = @" + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + window.DataContext = "foo"; + window.ApplyTemplate(); + + Assert.Equal("foobar", textBlock.Text); + } + } + [Fact] public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs new file mode 100644 index 0000000000..57570d8f5c --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; + +namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions +{ + public class TestValueConverter : IValueConverter + { + public string Append { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.ToString() + Append; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} From ea626a0e19cbec2c43b2d9fd859a9db2235a5376 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 23:48:14 +0200 Subject: [PATCH 039/110] Updated ncrunch config --- .../Avalonia.Direct2D1.RenderTests.v3.ncrunchproject | 1 + ...onia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject | 9 +++++++++ .ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject | 1 + src/Avalonia.Controls/Control.cs | 2 +- 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject index 235da29767..a8c3abe8f2 100644 --- a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject @@ -1,6 +1,7 @@  1000 + True True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject new file mode 100644 index 0000000000..15d9efad87 --- /dev/null +++ b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject @@ -0,0 +1,9 @@ + + + + + Avalonia.Markup.UnitTests.Data.Plugins.DataAnnotationsValidationPluginTests.Produces_Aggregate_BindingNotificationsx + + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject index 235da29767..a8c3abe8f2 100644 --- a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject @@ -1,6 +1,7 @@  1000 + True True \ No newline at end of file diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index c9a3b2940d..a2fc188000 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -292,7 +292,7 @@ namespace Avalonia.Controls if (value is ISetStyleParent setParent && setParent.ResourceParent == null) { setParent.SetParent(this); - } + } _styles.ResourcesChanged += StyleResourcesChanged; } From ac8cc99f16ce6491a859e3008ad460d8ab78012e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 00:54:26 +0200 Subject: [PATCH 040/110] Started adding MergedDictionaries. --- src/Avalonia.Controls/Control.cs | 13 +- .../Controls/IResourceDictionary.cs | 10 + .../Controls/IResourceProvider.cs | 2 +- .../Controls/ResourceDictionary.cs | 77 +++++++- .../ResourceDictionaryTests.cs | 175 ++++++++++++++++++ 5 files changed, 264 insertions(+), 13 deletions(-) create mode 100644 tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index a2fc188000..889edd48f8 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -284,7 +284,7 @@ namespace Avalonia.Controls if (_styles != null) { (_styles as ISetStyleParent)?.SetParent(null); - _styles.ResourcesChanged -= StyleResourcesChanged; + _styles.ResourcesChanged -= ThisResourcesChanged; } _styles = value; @@ -294,7 +294,7 @@ namespace Avalonia.Controls setParent.SetParent(this); } - _styles.ResourcesChanged += StyleResourcesChanged; + _styles.ResourcesChanged += ThisResourcesChanged; } } } @@ -323,7 +323,7 @@ namespace Avalonia.Controls if (_resources == null) { _resources = new ResourceDictionary(); - _resources.CollectionChanged += ResourceDictionaryChanged; + _resources.ResourcesChanged += ThisResourcesChanged; } return _resources; @@ -914,12 +914,7 @@ namespace Avalonia.Controls } } - private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e) - { - ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); - } - - private void StyleResourcesChanged(object sender, ResourcesChangedEventArgs e) + private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e) { ((ILogical)this).NotifyResourcesChanged(e); } diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs index bc76d8f60e..0ddeda0f3d 100644 --- a/src/Avalonia.Styling/Controls/IResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs @@ -11,6 +11,16 @@ namespace Avalonia.Controls /// public interface IResourceDictionary : IDictionary { + /// + /// Raised when resources in the dictionary are changed. + /// + event EventHandler ResourcesChanged; + + /// + /// Gets a collection of child resource dictionaries. + /// + IList MergedDictionaries { get; } + /// /// Tries to find a resource within the dictionary. /// diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index 180476b2e4..e133427350 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -8,7 +8,7 @@ namespace Avalonia.Controls public interface IResourceProvider { /// - /// Raised when the resources in the element are changed. + /// Raised when resources in the element are changed. /// event EventHandler ResourcesChanged; diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index bd08680a42..975d27c08b 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -2,7 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; using Avalonia.Collections; namespace Avalonia.Controls @@ -10,9 +11,79 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary, IDictionary + public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary { + private AvaloniaList _mergedDictionaries; + + public event EventHandler ResourcesChanged; + + public ResourceDictionary() + { + CollectionChanged += OnCollectionChanged; + } + + public IList MergedDictionaries + { + get + { + if (_mergedDictionaries == null) + { + _mergedDictionaries = new AvaloniaList(); + _mergedDictionaries.ResetBehavior = ResetBehavior.Remove; + _mergedDictionaries.ForEachItem( + x => + { + if (x.Count > 0) + { + OnResourcesChanged(); + } + + x.ResourcesChanged += MergedDictionaryResourcesChanged; + }, + x => + { + if (x.Count > 0) + { + OnResourcesChanged(); + } + + x.ResourcesChanged -= MergedDictionaryResourcesChanged; + }, + () => { }); + } + + return _mergedDictionaries; + } + } + /// - public bool TryGetResource(string key, out object value) => TryGetValue(key, out value); + public bool TryGetResource(string key, out object value) + { + if (TryGetValue(key, out value)) + { + return true; + } + + if (_mergedDictionaries != null) + { + for (var i = _mergedDictionaries.Count - 1; i >= 0; --i) + { + if (_mergedDictionaries[i].TryGetResource(key, out value)) + { + return true; + } + } + } + + return false; + } + + private void OnResourcesChanged() + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + + private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => OnResourcesChanged(); + private void MergedDictionaryResourcesChanged(object sender, ResourcesChangedEventArgs e) => OnResourcesChanged(); } } diff --git a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs new file mode 100644 index 0000000000..f31cbbac0a --- /dev/null +++ b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs @@ -0,0 +1,175 @@ +// 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 Avalonia.Controls; +using Xunit; + +namespace Avalonia.Styling.UnitTests +{ + public class ResourceDictionaryTests + { + [Fact] + public void TryGetResource_Should_Find_Resource() + { + var target = new ResourceDictionary + { + { "foo", "bar" }, + }; + + Assert.True(target.TryGetResource("foo", out var result)); + Assert.Equal("bar", result); + } + + [Fact] + public void TryGetResource_Should_Find_Resource_From_Merged_Dictionary() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary + { + { "foo", "bar" }, + } + } + }; + + Assert.True(target.TryGetResource("foo", out var result)); + Assert.Equal("bar", result); + } + + [Fact] + public void TryGetResource_Should_Find_Resource_From_Itself_Before_Merged_Dictionary() + { + var target = new ResourceDictionary + { + { "foo", "bar" }, + }; + + target.MergedDictionaries.Add(new ResourceDictionary + { + { "foo", "baz" }, + }); + + Assert.True(target.TryGetResource("foo", out var result)); + Assert.Equal("bar", result); + } + + [Fact] + public void TryGetResource_Should_Find_Resource_From_Later_Merged_Dictionary() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary + { + { "foo", "bar" }, + }, + new ResourceDictionary + { + { "foo", "baz" }, + } + } + }; + + Assert.True(target.TryGetResource("foo", out var result)); + Assert.Equal("baz", result); + } + + [Fact] + public void ResourcesChanged_Should_Be_Raised_On_Resource_Add() + { + var target = new ResourceDictionary(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Add() + { + var target = new ResourceDictionary(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries.Add(new ResourceDictionary + { + { "foo", "bar" }, + }); + + Assert.True(raised); + } + + [Fact] + public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Add() + { + var target = new ResourceDictionary(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries.Add(new ResourceDictionary()); + + Assert.False(raised); + } + + [Fact] + public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Remove() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary { { "foo", "bar" } }, + } + }; + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries.RemoveAt(0); + + Assert.True(raised); + } + + [Fact] + public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Remove() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary(), + } + }; + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries.RemoveAt(0); + + Assert.False(raised); + } + + [Fact] + public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Resource_Add() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary(), + } + }; + + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries[0].Add("foo", "bar"); + + Assert.True(raised); + } + } +} From e81b22b9d244ca4c10aa2db7d1e0df7833eed8f2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 00:55:06 +0200 Subject: [PATCH 041/110] IResourceProvider -> IResourceNode --- src/Avalonia.Controls/Application.cs | 8 ++++---- src/Avalonia.Controls/Control.cs | 6 +++--- src/Avalonia.Controls/IControl.cs | 2 +- .../Controls/{IResourceProvider.cs => IResourceNode.cs} | 4 ++-- .../Controls/ResourceProviderExtensions.cs | 6 +++--- src/Avalonia.Styling/Styling/ISetStyleParent.cs | 2 +- src/Avalonia.Styling/Styling/IStyle.cs | 2 +- src/Avalonia.Styling/Styling/Style.cs | 8 ++++---- src/Avalonia.Styling/Styling/Styles.cs | 6 +++--- .../MarkupExtensions/DynamicResourceExtension.cs | 8 ++++---- .../MarkupExtensions/StaticResourceExtension.cs | 4 ++-- src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs | 8 ++++---- tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs | 2 +- 13 files changed, 33 insertions(+), 33 deletions(-) rename src/Avalonia.Styling/Controls/{IResourceProvider.cs => IResourceNode.cs} (92%) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index ce15c0b9e3..5102c9a952 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -29,7 +29,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceProvider + public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode { /// /// The application-global data templates. @@ -126,10 +126,10 @@ namespace Avalonia IStyleHost IStyleHost.StylingParent => null; /// - bool IResourceProvider.HasResources => _resources?.Count > 0; + bool IResourceNode.HasResources => _resources?.Count > 0; /// - IResourceProvider IResourceProvider.ResourceParent => null; + IResourceNode IResourceNode.ResourceParent => null; /// /// Initializes the application by loading XAML etc. @@ -159,7 +159,7 @@ namespace Avalonia } /// - bool IResourceProvider.TryGetResource(string key, out object value) + bool IResourceNode.TryGetResource(string key, out object value) { value = null; return (_resources?.TryGetResource(key, out value) ?? false) || diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 889edd48f8..a05dccec58 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -390,10 +390,10 @@ namespace Avalonia.Controls IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren; /// - bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; + bool IResourceNode.HasResources => _resources?.Count > 0 || Styles.HasResources; /// - IResourceProvider IResourceProvider.ResourceParent => ((IStyleHost)this).StylingParent as IResourceProvider; + IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode; /// IAvaloniaReadOnlyList IStyleable.Classes => Classes; @@ -480,7 +480,7 @@ namespace Avalonia.Controls } /// - bool IResourceProvider.TryGetResource(string key, out object value) + bool IResourceNode.TryGetResource(string key, out object value) { value = null; return (_resources?.TryGetResource(key, out value) ?? false) || diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index 37ad12cf91..a5730bf398 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// /// Interface for Avalonia controls. /// - public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IResourceProvider, IStyleable, IStyleHost + public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IResourceNode, IStyleable, IStyleHost { /// /// Occurs when the control has finished initialization. diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceNode.cs similarity index 92% rename from src/Avalonia.Styling/Controls/IResourceProvider.cs rename to src/Avalonia.Styling/Controls/IResourceNode.cs index e133427350..54cf39e3b5 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceNode.cs @@ -5,7 +5,7 @@ namespace Avalonia.Controls /// /// Defines an element that can be queried for resources. /// - public interface IResourceProvider + public interface IResourceNode { /// /// Raised when resources in the element are changed. @@ -20,7 +20,7 @@ namespace Avalonia.Controls /// /// Gets the parent resource provider, if any. /// - IResourceProvider ResourceParent { get; } + IResourceNode ResourceParent { get; } /// /// Tries to find a resource within the element. diff --git a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs index 45e16438d0..c96e8ea7f3 100644 --- a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// The control. /// The resource key. /// The resource, or if not found. - public static object FindResource(this IResourceProvider control, string key) + public static object FindResource(this IResourceNode control, string key) { Contract.Requires(control != null); Contract.Requires(key != null); @@ -23,7 +23,7 @@ namespace Avalonia.Controls while (current != null) { - if (current is IResourceProvider host) + if (current is IResourceNode host) { if (host.TryGetResource(key, out var value)) { @@ -37,7 +37,7 @@ namespace Avalonia.Controls return AvaloniaProperty.UnsetValue; } - public static IObservable GetResourceObservable(this IResourceProvider target, string key) + public static IObservable GetResourceObservable(this IResourceNode target, string key) { return Observable.FromEventPattern( x => target.ResourcesChanged += x, diff --git a/src/Avalonia.Styling/Styling/ISetStyleParent.cs b/src/Avalonia.Styling/Styling/ISetStyleParent.cs index da5b34798e..9f5855b401 100644 --- a/src/Avalonia.Styling/Styling/ISetStyleParent.cs +++ b/src/Avalonia.Styling/Styling/ISetStyleParent.cs @@ -17,7 +17,7 @@ namespace Avalonia.Styling /// Sets the style parent. /// /// The parent. - void SetParent(IResourceProvider parent); + void SetParent(IResourceNode parent); /// /// Notifies the style that a change has been made to resources that apply to it. diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs index aa8980ddde..5f12763825 100644 --- a/src/Avalonia.Styling/Styling/IStyle.cs +++ b/src/Avalonia.Styling/Styling/IStyle.cs @@ -8,7 +8,7 @@ namespace Avalonia.Styling /// /// Defines the interface for styles. /// - public interface IStyle : IResourceProvider + public interface IStyle : IResourceNode { /// /// Attaches the style to a control if the style's selector matches. diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 54b53868da..a0d4c8c087 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -17,7 +17,7 @@ namespace Avalonia.Styling { private static Dictionary> _applied = new Dictionary>(); - private IResourceProvider _parent; + private IResourceNode _parent; private ResourceDictionary _resources; /// @@ -68,10 +68,10 @@ namespace Avalonia.Styling public IList Setters { get; set; } = new List(); /// - bool IResourceProvider.HasResources => _resources?.Count > 0; + bool IResourceNode.HasResources => _resources?.Count > 0; /// - IResourceProvider IResourceProvider.ResourceParent => _parent; + IResourceNode IResourceNode.ResourceParent => _parent; /// /// Attaches the style to a control if the style's selector matches. @@ -139,7 +139,7 @@ namespace Avalonia.Styling } /// - void ISetStyleParent.SetParent(IResourceProvider parent) + void ISetStyleParent.SetParent(IResourceNode parent) { if (_parent != null && parent != null) { diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 43a542d460..767d5ce463 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling /// public class Styles : AvaloniaList, IStyle, ISetStyleParent { - private IResourceProvider _parent; + private IResourceNode _parent; private ResourceDictionary _resources; public Styles() @@ -78,7 +78,7 @@ namespace Avalonia.Styling } /// - IResourceProvider IResourceProvider.ResourceParent => _parent; + IResourceNode IResourceNode.ResourceParent => _parent; /// /// Attaches the style to a control if the style's selector matches. @@ -116,7 +116,7 @@ namespace Avalonia.Styling } /// - void ISetStyleParent.SetParent(IResourceProvider parent) + void ISetStyleParent.SetParent(IResourceNode parent) { if (_parent != null && parent != null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 7937be60aa..5e421b7e73 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -15,7 +15,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { public class DynamicResourceExtension : MarkupExtension, IBinding { - private IResourceProvider _anchor; + private IResourceNode _anchor; public DynamicResourceExtension() { @@ -33,9 +33,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var context = (ITypeDescriptorContext)serviceProvider; var provideTarget = context.GetService(); - if (!(provideTarget.TargetObject is IResourceProvider)) + if (!(provideTarget.TargetObject is IResourceNode)) { - _anchor = GetAnchor(context); + _anchor = GetAnchor(context); } return this; @@ -47,7 +47,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions object anchor, bool enableDataValidation) { - var control = target as IResourceProvider ?? _anchor; + var control = target as IResourceNode ?? _anchor; if (control != null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index e3cd7b062b..4764677ede 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -31,7 +31,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var context = (ITypeDescriptorContext)serviceProvider; var schemaContext = context.GetService()?.SchemaContext; var ambientProvider = context.GetService(); - var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceProvider)); + var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceNode)); var ambientValues = ambientProvider.GetAllAmbientValues(resourceProviderType); // Look upwards though the ambient context for IResourceProviders which might be able @@ -47,7 +47,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { // We override XamlType.CanAssignTo in BindingXamlType so the results we get back // from GetAllAmbientValues aren't necessarily of the correct type. - if (ambientValue is IResourceProvider resourceProvider) + if (ambientValue is IResourceNode resourceProvider) { if (resourceProvider is IControl control && control.StylingParent != null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index a121cd8c1b..c1867da9c0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -14,7 +14,7 @@ namespace Avalonia.Markup.Xaml.Styling { private Uri _baseUri; private IStyle _loaded; - private IResourceProvider _parent; + private IResourceNode _parent; /// /// Initializes a new instance of the class. @@ -52,10 +52,10 @@ namespace Avalonia.Markup.Xaml.Styling } /// - bool IResourceProvider.HasResources => Loaded.HasResources; + bool IResourceNode.HasResources => Loaded.HasResources; /// - IResourceProvider IResourceProvider.ResourceParent => _parent; + IResourceNode IResourceNode.ResourceParent => _parent; /// public void Attach(IStyleable control, IStyleHost container) @@ -76,7 +76,7 @@ namespace Avalonia.Markup.Xaml.Styling } /// - void ISetStyleParent.SetParent(IResourceProvider parent) + void ISetStyleParent.SetParent(IResourceNode parent) { if (_parent != null && parent != null) { diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index 8bf6102f27..98aaa29261 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -162,7 +162,7 @@ namespace Avalonia.Layout.UnitTests private void RegisterServices() { var globalStyles = new Mock(); - var globalStylesResources = globalStyles.As(); + var globalStylesResources = globalStyles.As(); var outObj = (object)10; globalStylesResources.Setup(x => x.TryGetResource("FontSizeNormal", out outObj)).Returns(true); From 0a6216a35226afb3c8884346220f41b1867f3ba2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 01:15:12 +0200 Subject: [PATCH 042/110] Doc comments. --- src/Avalonia.Styling/Controls/IResourceNode.cs | 4 ++-- src/Avalonia.Styling/Controls/ResourceDictionary.cs | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Styling/Controls/IResourceNode.cs b/src/Avalonia.Styling/Controls/IResourceNode.cs index 54cf39e3b5..6ff5fb8d2f 100644 --- a/src/Avalonia.Styling/Controls/IResourceNode.cs +++ b/src/Avalonia.Styling/Controls/IResourceNode.cs @@ -13,12 +13,12 @@ namespace Avalonia.Controls event EventHandler ResourcesChanged; /// - /// Gets a value indicating whether the provider has resources. + /// Gets a value indicating whether the node has resources. /// bool HasResources { get; } /// - /// Gets the parent resource provider, if any. + /// Gets the parent resource node, if any. /// IResourceNode ResourceParent { get; } diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 975d27c08b..30fd8056f1 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -15,13 +15,18 @@ namespace Avalonia.Controls { private AvaloniaList _mergedDictionaries; - public event EventHandler ResourcesChanged; - + /// + /// Initializes a new instance of the class. + /// public ResourceDictionary() { CollectionChanged += OnCollectionChanged; } + /// + public event EventHandler ResourcesChanged; + + /// public IList MergedDictionaries { get From 91387a74e1ff158b67314c9273842147b958315b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 02:00:39 +0200 Subject: [PATCH 043/110] Start testing MergedDictionaries. --- src/Avalonia.Controls/Application.cs | 27 ++++++++- src/Avalonia.Controls/Control.cs | 23 ++++++-- .../Properties/AssemblyInfo.cs | 2 + src/Avalonia.Styling/Styling/Style.cs | 27 ++++++--- src/Avalonia.Styling/Styling/Styles.cs | 32 ++++++---- .../StaticResourceExtensionTests.cs | 58 +++++++++++++++++++ 6 files changed, 141 insertions(+), 28 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 5102c9a952..8c5f9abb8d 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -39,7 +39,7 @@ namespace Avalonia private readonly Lazy _clipboard = new Lazy(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))); private readonly Styler _styler = new Styler(); - private ResourceDictionary _resources; + private IResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -107,7 +107,30 @@ namespace Avalonia /// /// Gets the application's global resource dictionary. /// - public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary()); + public IResourceDictionary Resources + { + get => _resources ?? (Resources = new ResourceDictionary()); + set + { + Contract.Requires(value != null); + + var hadResources = false; + + if (_resources != null) + { + hadResources = _resources.Count > 0; + _resources.ResourcesChanged -= ResourcesChanged; + } + + _resources = value; + _resources.ResourcesChanged += ResourcesChanged; + + if (hadResources || _resources.Count > 0) + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + } + } /// /// Gets the application's global styles. diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index a05dccec58..bde9bb6760 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -97,7 +97,7 @@ namespace Avalonia.Controls private bool _isAttachedToLogicalTree; private IAvaloniaList _logicalChildren; private INameScope _nameScope; - private ResourceDictionary _resources; + private IResourceDictionary _resources; private Styles _styles; private bool _styled; private Subject _styleDetach = new Subject(); @@ -318,15 +318,26 @@ namespace Avalonia.Controls /// public IResourceDictionary Resources { - get + get => _resources ?? (Resources = new ResourceDictionary()); + set { - if (_resources == null) + Contract.Requires(value != null); + + var hadResources = false; + + if (_resources != null) { - _resources = new ResourceDictionary(); - _resources.ResourcesChanged += ThisResourcesChanged; + hadResources = _resources.Count > 0; + _resources.ResourcesChanged -= ThisResourcesChanged; } - return _resources; + _resources = value; + _resources.ResourcesChanged += ThisResourcesChanged; + + if (hadResources || _resources.Count > 0) + { + ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } } } diff --git a/src/Avalonia.Styling/Properties/AssemblyInfo.cs b/src/Avalonia.Styling/Properties/AssemblyInfo.cs index b53681aeed..0a639139f7 100644 --- a/src/Avalonia.Styling/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Styling/Properties/AssemblyInfo.cs @@ -6,5 +6,7 @@ using System.Runtime.CompilerServices; using Avalonia.Metadata; [assembly: AssemblyTitle("Avalonia.Styling")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.LogicalTree")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Styling")] [assembly: InternalsVisibleTo("Avalonia.Styling.UnitTests")] \ No newline at end of file diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index a0d4c8c087..6e79ee038e 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -18,7 +18,7 @@ namespace Avalonia.Styling private static Dictionary> _applied = new Dictionary>(); private IResourceNode _parent; - private ResourceDictionary _resources; + private IResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -44,15 +44,26 @@ namespace Avalonia.Styling /// public IResourceDictionary Resources { - get + get => _resources ?? (Resources = new ResourceDictionary()); + set { - if (_resources == null) + Contract.Requires(value != null); + + var hadResources = false; + + if (_resources != null) { - _resources = new ResourceDictionary(); - _resources.CollectionChanged += ResourceDictionaryChanged; + hadResources = _resources.Count > 0; + _resources.ResourcesChanged -= ResourceDictionaryChanged; } - return _resources; + _resources = value; + _resources.ResourcesChanged += ResourceDictionaryChanged; + + if (hadResources || _resources.Count > 0) + { + ((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } } } @@ -180,9 +191,9 @@ namespace Avalonia.Styling _applied.Remove(control); } - private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e) + private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e) { - ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + ResourcesChanged?.Invoke(this, e); } } } diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 767d5ce463..714e7f6def 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; using Avalonia.Controls; @@ -15,7 +14,7 @@ namespace Avalonia.Styling public class Styles : AvaloniaList, IStyle, ISetStyleParent { private IResourceNode _parent; - private ResourceDictionary _resources; + private IResourceDictionary _resources; public Styles() { @@ -65,15 +64,26 @@ namespace Avalonia.Styling /// public IResourceDictionary Resources { - get + get => _resources ?? (Resources = new ResourceDictionary()); + set { - if (_resources == null) + Contract.Requires(value != null); + + var hadResources = false; + + if (_resources != null) { - _resources = new ResourceDictionary(); - _resources.CollectionChanged += ResourceDictionaryChanged; + hadResources = _resources.Count > 0; + _resources.ResourcesChanged -= ResourceDictionaryChanged; } - return _resources; + _resources = value; + _resources.ResourcesChanged += ResourceDictionaryChanged; + + if (hadResources || _resources.Count > 0) + { + ((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } } } @@ -132,16 +142,14 @@ namespace Avalonia.Styling ResourcesChanged?.Invoke(this, e); } - private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e) + private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e) { - var ev = new ResourcesChangedEventArgs(); - foreach (var child in this) { - (child as ISetStyleParent)?.NotifyResourcesChanged(ev); + (child as ISetStyleParent)?.NotifyResourcesChanged(e); } - ResourcesChanged?.Invoke(this, ev); + ResourcesChanged?.Invoke(this, e); } private void SubResourceChanged(object sender, ResourcesChangedEventArgs e) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index ed3ab3cc52..862ce2b3c0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -104,6 +104,64 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void StaticResource_From_MergedDictionary_Can_Be_Assigned_To_Property() + { + var xaml = @" + + + + + + #ff506070 + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + + [Fact] + public void StaticResource_From_MergedDictionary_In_Style_Can_Be_Assigned_To_Property() + { + var xaml = @" + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + [Fact] public void StaticResource_From_Application_Can_Be_Assigned_To_Property_In_UserControl() { From 56c06be403c58d456f54131d7e86923f9374c9e8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 08:30:57 +0200 Subject: [PATCH 044/110] DynamicResource merged dictionary tests. --- .../DynamicResourceExtensionTests.cs | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 7de60a1029..c751f9e056 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -76,6 +76,68 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + + [Fact] + public void DynamicResource_From_MergedDictionary_Can_Be_Assigned_To_Property() + { + var xaml = @" + + + + + + #ff506070 + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + + [Fact] + public void DynamicResource_From_MergedDictionary_In_Style_Can_Be_Assigned_To_Property() + { + var xaml = @" + + + + + "; @@ -399,6 +461,99 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + [Fact] + public void DynamicResource_Tracks_Added_MergedResource() + { + var xaml = @" + + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + Assert.Null(border.Background); + + userControl.Resources.MergedDictionaries[0].Add("brush", new SolidColorBrush(0xff506070)); + + var brush = (SolidColorBrush)border.Background; + Assert.NotNull(brush); + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + + [Fact] + public void DynamicResource_Tracks_Added_MergedResource_Dictionary() + { + var xaml = @" + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + Assert.Null(border.Background); + + var dictionary = new ResourceDictionary + { + { "brush", new SolidColorBrush(0xff506070) }, + }; + + userControl.Resources.MergedDictionaries.Add(dictionary); + + var brush = (SolidColorBrush)border.Background; + Assert.NotNull(brush); + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + + [Fact] + public void DynamicResource_Tracks_Added_Style_MergedResource_Dictionary() + { + var xaml = @" + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + Assert.Null(border.Background); + + var dictionary = new ResourceDictionary + { + { "brush", new SolidColorBrush(0xff506070) }, + }; + + ((Style)userControl.Styles[0]).Resources.MergedDictionaries.Add(dictionary); + + var brush = (SolidColorBrush)border.Background; + Assert.NotNull(brush); + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + [Fact] public void DynamicResource_Can_Be_Found_Across_Xaml_Style_Files() { From 426cd8c9ddeee12685cea2e5e534b19f4e694dec Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Fri, 25 Aug 2017 12:55:36 +0300 Subject: [PATCH 045/110] ToolTip: IsOpen, Placement, Offset --- samples/ControlCatalog/Pages/ToolTipPage.xaml | 48 ++-- src/Avalonia.Controls/Primitives/Popup.cs | 26 +- src/Avalonia.Controls/ToolTip.cs | 236 ++++++++++++------ src/Avalonia.Controls/ToolTipService.cs | 98 ++++++++ 4 files changed, 300 insertions(+), 108 deletions(-) create mode 100644 src/Avalonia.Controls/ToolTipService.cs diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml index 29df11510c..79114bc9de 100644 --- a/samples/ControlCatalog/Pages/ToolTipPage.xaml +++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml @@ -1,22 +1,34 @@ - - ToolTip - A control which pops up a hint when a control is hovered + + ToolTip + A control which pops up a hint when a control is hovered - - - - - ToolTip - A control which pops up a hint when a control is hovered - - - Hover Here - + + + + + + ToolTip + A control which pops up a hint when a control is hovered + + + Hover Here + + + And Here + + - \ No newline at end of file diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index daea187a69..5cd3b22fc9 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -277,7 +277,7 @@ namespace Avalonia.Controls.Primitives { base.OnDetachedFromLogicalTree(e); _topLevel = null; - + if (_popupRoot != null) { ((ISetLogicalParent)_popupRoot).SetParent(null); @@ -327,34 +327,40 @@ namespace Avalonia.Controls.Primitives /// /// The popup's position in screen coordinates. protected virtual Point GetPosition() + { + return GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, + HorizontalOffset, VerticalOffset); + } + + internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset) { var zero = default(Point); - var mode = PlacementMode; - var target = PlacementTarget ?? this.GetVisualParent(); + var mode = placement; if (target?.GetVisualRoot() == null) { mode = PlacementMode.Pointer; - } + } switch (mode) { case PlacementMode.Pointer: - if(PopupRoot != null) + if (popupRoot != null) { // Scales the Horizontal and Vertical offset to screen co-ordinates. - var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling); - return (((IInputRoot)PopupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset; + var screenOffset = new Point(horizontalOffset * (popupRoot as ILayoutRoot).LayoutScaling, + verticalOffset * (popupRoot as ILayoutRoot).LayoutScaling); + return (((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset; } return default(Point); case PlacementMode.Bottom: - - return target?.PointToScreen(new Point(0 + HorizontalOffset, target.Bounds.Height + VerticalOffset)) ?? zero; + return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? + zero; case PlacementMode.Right: - return target?.PointToScreen(new Point(target.Bounds.Width + HorizontalOffset, 0 + VerticalOffset)) ?? zero; + return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? zero; default: throw new InvalidOperationException("Invalid value for Popup.PlacementMode"); diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index e1b69637af..e45f30f818 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -3,11 +3,7 @@ using System; using System.Reactive.Linq; -using System.Reactive.Subjects; using Avalonia.Controls.Primitives; -using Avalonia.Input; -using Avalonia.Threading; -using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -29,29 +25,50 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterAttached("Tip"); /// - /// The popup window used to display the active tooltip. + /// Defines the ToolTip.IsOpen attached property. /// - private static PopupRoot s_popup; + public static readonly AttachedProperty IsOpenProperty = + AvaloniaProperty.RegisterAttached("IsOpen"); /// - /// The control that the currently visible tooltip is attached to. + /// Defines the ToolTip.Placement property. /// - private static Control s_current; + public static readonly AttachedProperty PlacementProperty = + AvaloniaProperty.RegisterAttached("Placement", defaultValue: PlacementMode.Pointer); /// - /// Observable fired when a tooltip should be displayed for a control. The output from this - /// observable is throttled and calls when the time - /// period expires. + /// Defines the ToolTip.HorizontalOffset property. /// - private static readonly Subject s_show = new Subject(); + public static readonly AttachedProperty HorizontalOffsetProperty = + AvaloniaProperty.RegisterAttached("HorizontalOffset"); + + /// + /// Defines the ToolTip.VerticalOffset property. + /// + public static readonly AttachedProperty VerticalOffsetProperty = + AvaloniaProperty.RegisterAttached("VerticalOffset", 20); + + /// + /// Defines the ToolTip.ShowDelay property. + /// + public static readonly AttachedProperty ShowDelayProperty = + AvaloniaProperty.RegisterAttached("ShowDelay", 400); + + /// + /// Stores the curernt instance in the control. + /// + private static readonly AttachedProperty ToolTipProperty = + AvaloniaProperty.RegisterAttached("ToolTip"); + + private PopupRoot _popup; /// /// Initializes static members of the class. /// static ToolTip() { - TipProperty.Changed.Subscribe(TipChanged); - s_show.Throttle(TimeSpan.FromSeconds(0.5), AvaloniaScheduler.Instance).Subscribe(ShowToolTip); + TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged); + IsOpenProperty.Changed.Subscribe(IsOpenChanged); } /// @@ -77,101 +94,160 @@ namespace Avalonia.Controls } /// - /// called when the property changes on a control. + /// Gets the value of the ToolTip.IsOpen attached property. /// - /// The event args. - private static void TipChanged(AvaloniaPropertyChangedEventArgs e) + /// The control to get the property from. + /// + /// A value indicating whether the tool tip is visible. + /// + public static bool GetIsOpen(Control element) { - var control = (Control)e.Sender; + return element.GetValue(IsOpenProperty); + } - if (e.OldValue != null) - { - control.PointerEnter -= ControlPointerEnter; - control.PointerLeave -= ControlPointerLeave; - } + /// + /// Sets the value of the ToolTip.IsOpen attached property. + /// + /// The control to get the property from. + /// A value indicating whether the tool tip is visible. + public static void SetIsOpen(Control element, bool value) + { + element.SetValue(IsOpenProperty, value); + } - if (e.NewValue != null) - { - control.PointerEnter += ControlPointerEnter; - control.PointerLeave += ControlPointerLeave; - } + /// + /// Gets the value of the ToolTip.Placement attached property. + /// + /// The control to get the property from. + /// + /// A value indicating how the tool tip is positioned. + /// + public static PlacementMode GetPlacement(Control element) + { + return element.GetValue(PlacementProperty); } /// - /// Shows a tooltip for the specified control. + /// Sets the value of the ToolTip.Placement attached property. /// - /// The control. - private static void ShowToolTip(Control control) + /// The control to get the property from. + /// A value indicating how the tool tip is positioned. + public static void SetPlacement(Control element, PlacementMode value) { - if (control != null && control.IsVisible && control.GetVisualRoot() != null) - { - var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control); + element.SetValue(PlacementProperty, value); + } - if (cp.HasValue && control.IsVisible && new Rect(control.Bounds.Size).Contains(cp.Value)) - { - var position = control.PointToScreen(cp.Value) + new Vector(0, 22); - - if (s_popup == null) - { - s_popup = new PopupRoot(); - s_popup.Content = new ToolTip(); - } - else - { - ((ISetLogicalParent)s_popup).SetParent(null); - } - - ((ISetLogicalParent)s_popup).SetParent(control); - ((ToolTip)s_popup.Content).Content = GetTip(control); - s_popup.Position = position; - s_popup.Show(); - - s_current = control; - } - } + /// + /// Gets the value of the ToolTip.HorizontalOffset attached property. + /// + /// The control to get the property from. + /// + /// A value indicating how the tool tip is positioned. + /// + public static double GetHorizontalOffset(Control element) + { + return element.GetValue(HorizontalOffsetProperty); + } + + /// + /// Sets the value of the ToolTip.HorizontalOffset attached property. + /// + /// The control to get the property from. + /// A value indicating how the tool tip is positioned. + public static void SetHorizontalOffset(Control element, double value) + { + element.SetValue(HorizontalOffsetProperty, value); + } + + /// + /// Gets the value of the ToolTip.VerticalOffset attached property. + /// + /// The control to get the property from. + /// + /// A value indicating how the tool tip is positioned. + /// + public static double GetVerticalOffset(Control element) + { + return element.GetValue(VerticalOffsetProperty); + } + + /// + /// Sets the value of the ToolTip.VerticalOffset attached property. + /// + /// The control to get the property from. + /// A value indicating how the tool tip is positioned. + public static void SetVerticalOffset(Control element, double value) + { + element.SetValue(VerticalOffsetProperty, value); } /// - /// Called when the pointer enters a control with an attached tooltip. + /// Gets the value of the ToolTip.ShowDelay attached property. /// - /// The event sender. - /// The event args. - private static void ControlPointerEnter(object sender, PointerEventArgs e) + /// The control to get the property from. + /// + /// A value indicating the time, in milliseconds, before a tool tip opens. + /// + public static int GetShowDelay(Control element) { - s_current = (Control)sender; - s_show.OnNext(s_current); + return element.GetValue(ShowDelayProperty); } /// - /// Called when the pointer leaves a control with an attached tooltip. + /// Sets the value of the ToolTip.ShowDelay attached property. /// - /// The event sender. - /// The event args. - private static void ControlPointerLeave(object sender, PointerEventArgs e) + /// The control to get the property from. + /// A value indicating the time, in milliseconds, before a tool tip opens. + public static void SetShowDelay(Control element, int value) { - var control = (Control)sender; + element.SetValue(ShowDelayProperty, value); + } + + private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e) + { + var control = (Control)e.Sender; - if (control == s_current) + if ((bool)e.NewValue) { - if (s_popup != null) + var tip = GetTip(control); + if (tip == null) return; + + var toolTip = control.GetValue(ToolTipProperty); + if (toolTip == null || (tip != toolTip && tip != toolTip.Content)) { - DisposeTooltip(); - s_show.OnNext(null); + toolTip?.Close(); + + toolTip = tip as ToolTip ?? new ToolTip { Content = tip }; + control.SetValue(ToolTipProperty, toolTip); } + + toolTip.Open(control); + } + else + { + var toolTip = control.GetValue(ToolTipProperty); + toolTip?.Close(); } } - private static void DisposeTooltip() + private void Open(Control control) { - if (s_popup != null) - { - // Clear the ToolTip's Content in case it has control content: this will - // reset its visual parent allowing it to be used again. - ((ToolTip)s_popup.Content).Content = null; + Close(); + + _popup = new PopupRoot { Content = this }; + ((ISetLogicalParent)_popup).SetParent(control); + _popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup, + GetHorizontalOffset(control), GetVerticalOffset(control)); + _popup.Show(); + } - // Dispose of the popup. - s_popup.Dispose(); - s_popup = null; + private void Close() + { + if (_popup != null) + { + _popup.Content = null; + _popup.Hide(); + _popup = null; } } } diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs new file mode 100644 index 0000000000..bfd7ef0f33 --- /dev/null +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -0,0 +1,98 @@ +using System; +using Avalonia.Input; +using Avalonia.Threading; + +namespace Avalonia.Controls +{ + /// + /// Handeles interaction with controls. + /// + internal sealed class ToolTipService + { + public static ToolTipService Instance { get; } = new ToolTipService(); + + private DispatcherTimer _timer; + + private ToolTipService() { } + + /// + /// called when the property changes on a control. + /// + /// The event args. + internal void TipChanged(AvaloniaPropertyChangedEventArgs e) + { + var control = (Control)e.Sender; + + if (e.OldValue != null) + { + control.PointerEnter -= ControlPointerEnter; + control.PointerLeave -= ControlPointerLeave; + } + + if (e.NewValue != null) + { + control.PointerEnter += ControlPointerEnter; + control.PointerLeave += ControlPointerLeave; + } + } + + /// + /// Called when the pointer enters a control with an attached tooltip. + /// + /// The event sender. + /// The event args. + private void ControlPointerEnter(object sender, PointerEventArgs e) + { + StopTimer(); + + var control = (Control)sender; + var showDelay = ToolTip.GetShowDelay(control); + if (showDelay == 0) + { + Open(control); + } + else + { + StartShowTimer(showDelay, control); + } + } + + /// + /// Called when the pointer leaves a control with an attached tooltip. + /// + /// The event sender. + /// The event args. + private void ControlPointerLeave(object sender, PointerEventArgs e) + { + var control = (Control)sender; + Close(control); + } + + private void StartShowTimer(int showDelay, Control control) + { + _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(showDelay) }; + _timer.Tick += (o, e) => Open(control); + _timer.Start(); + } + + private void Open(Control control) + { + StopTimer(); + + ToolTip.SetIsOpen(control, true); + } + + private void Close(Control control) + { + StopTimer(); + + ToolTip.SetIsOpen(control, false); + } + + private void StopTimer() + { + _timer?.Stop(); + _timer = null; + } + } +} \ No newline at end of file From a72ce3671d6b6917186813e5cec5d136c1d3f7ef Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 19:01:21 +0200 Subject: [PATCH 046/110] Added ResourceInclude. --- src/Avalonia.Controls/Application.cs | 4 +- src/Avalonia.Controls/Control.cs | 4 +- .../Controls/IResourceDictionary.cs | 21 +------ .../Controls/IResourceNode.cs | 26 +------- .../Controls/IResourceProvider.cs | 32 ++++++++++ .../Controls/ResourceDictionary.cs | 17 +++-- src/Avalonia.Styling/Styling/Style.cs | 4 +- .../Avalonia.Markup.Xaml.csproj | 1 + .../Data/ResourceInclude.cs | 63 +++++++++++++++++++ .../Styling/StyleInclude.cs | 2 +- .../Data/ResourceIncludeTests.cs | 55 ++++++++++++++++ .../DynamicResourceExtensionTests.cs | 2 +- .../ResourceDictionaryTests.cs | 2 +- 13 files changed, 176 insertions(+), 57 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/IResourceProvider.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 8c5f9abb8d..725163bba8 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -149,7 +149,7 @@ namespace Avalonia IStyleHost IStyleHost.StylingParent => null; /// - bool IResourceNode.HasResources => _resources?.Count > 0; + bool IResourceProvider.HasResources => _resources?.Count > 0; /// IResourceNode IResourceNode.ResourceParent => null; @@ -182,7 +182,7 @@ namespace Avalonia } /// - bool IResourceNode.TryGetResource(string key, out object value) + bool IResourceProvider.TryGetResource(string key, out object value) { value = null; return (_resources?.TryGetResource(key, out value) ?? false) || diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index bde9bb6760..b6283ab7ee 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -401,7 +401,7 @@ namespace Avalonia.Controls IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren; /// - bool IResourceNode.HasResources => _resources?.Count > 0 || Styles.HasResources; + bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; /// IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode; @@ -491,7 +491,7 @@ namespace Avalonia.Controls } /// - bool IResourceNode.TryGetResource(string key, out object value) + bool IResourceProvider.TryGetResource(string key, out object value) { value = null; return (_resources?.TryGetResource(key, out value) ?? false) || diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs index 0ddeda0f3d..e6da375544 100644 --- a/src/Avalonia.Styling/Controls/IResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs @@ -9,28 +9,11 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public interface IResourceDictionary : IDictionary + public interface IResourceDictionary : IResourceProvider, IDictionary { - /// - /// Raised when resources in the dictionary are changed. - /// - event EventHandler ResourcesChanged; - /// /// Gets a collection of child resource dictionaries. /// - IList MergedDictionaries { get; } - - /// - /// Tries to find a resource within the dictionary. - /// - /// The resource key. - /// - /// When this method returns, contains the value associated with the specified key, - /// if the key is found; otherwise, null - /// - /// True if the resource if found, otherwise false. - /// - bool TryGetResource(string key, out object value); + IList MergedDictionaries { get; } } } diff --git a/src/Avalonia.Styling/Controls/IResourceNode.cs b/src/Avalonia.Styling/Controls/IResourceNode.cs index 6ff5fb8d2f..b6a6cdc3e3 100644 --- a/src/Avalonia.Styling/Controls/IResourceNode.cs +++ b/src/Avalonia.Styling/Controls/IResourceNode.cs @@ -3,35 +3,13 @@ namespace Avalonia.Controls { /// - /// Defines an element that can be queried for resources. + /// Represents resource provider in a tree. /// - public interface IResourceNode + public interface IResourceNode : IResourceProvider { - /// - /// Raised when resources in the element are changed. - /// - event EventHandler ResourcesChanged; - - /// - /// Gets a value indicating whether the node has resources. - /// - bool HasResources { get; } - /// /// Gets the parent resource node, if any. /// IResourceNode ResourceParent { get; } - - /// - /// Tries to find a resource within the element. - /// - /// The resource key. - /// - /// When this method returns, contains the value associated with the specified key, - /// if the key is found; otherwise, null - /// - /// True if the resource if found, otherwise false. - /// - bool TryGetResource(string key, out object value); } } diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs new file mode 100644 index 0000000000..2ca83ea2d2 --- /dev/null +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -0,0 +1,32 @@ +using System; + +namespace Avalonia.Controls +{ + /// + /// Represents an object that can be queried for resources. + /// + public interface IResourceProvider + { + /// + /// Raised when resources in the provider are changed. + /// + event EventHandler ResourcesChanged; + + /// + /// Gets a value indicating whether the element has resources. + /// + bool HasResources { get; } + + /// + /// Tries to find a resource within the provider. + /// + /// The resource key. + /// + /// When this method returns, contains the value associated with the specified key, + /// if the key is found; otherwise, null + /// + /// True if the resource if found, otherwise false. + /// + bool TryGetResource(string key, out object value); + } +} diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 30fd8056f1..ec0a59dad9 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using Avalonia.Collections; namespace Avalonia.Controls @@ -13,7 +14,7 @@ namespace Avalonia.Controls /// public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary { - private AvaloniaList _mergedDictionaries; + private AvaloniaList _mergedDictionaries; /// /// Initializes a new instance of the class. @@ -27,18 +28,18 @@ namespace Avalonia.Controls public event EventHandler ResourcesChanged; /// - public IList MergedDictionaries + public IList MergedDictionaries { get { if (_mergedDictionaries == null) { - _mergedDictionaries = new AvaloniaList(); + _mergedDictionaries = new AvaloniaList(); _mergedDictionaries.ResetBehavior = ResetBehavior.Remove; _mergedDictionaries.ForEachItem( x => { - if (x.Count > 0) + if (x.HasResources) { OnResourcesChanged(); } @@ -47,7 +48,7 @@ namespace Avalonia.Controls }, x => { - if (x.Count > 0) + if (x.HasResources) { OnResourcesChanged(); } @@ -61,6 +62,12 @@ namespace Avalonia.Controls } } + /// + bool IResourceProvider.HasResources + { + get => Count > 0 || (_mergedDictionaries?.Any(x => x.HasResources) ?? false); + } + /// public bool TryGetResource(string key, out object value) { diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 6e79ee038e..4182ffada3 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -79,10 +79,10 @@ namespace Avalonia.Styling public IList Setters { get; set; } = new List(); /// - bool IResourceNode.HasResources => _resources?.Count > 0; + IResourceNode IResourceNode.ResourceParent => _parent; /// - IResourceNode IResourceNode.ResourceParent => _parent; + bool IResourceProvider.HasResources => _resources?.Count > 0; /// /// Attaches the style to a control if the style's selector matches. diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 5c2d228e62..f04f148c5b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -34,6 +34,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs new file mode 100644 index 0000000000..035765fae0 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs @@ -0,0 +1,63 @@ +using System; +using System.ComponentModel; +using Avalonia.Controls; +using Portable.Xaml.ComponentModel; +using Portable.Xaml.Markup; + +namespace Avalonia.Markup.Xaml.Data +{ + /// + /// Loads a resource dictionary from a specified URL. + /// + public class ResourceInclude : MarkupExtension, IResourceProvider + { + private Uri _baseUri; + private IResourceDictionary _loaded; + + public event EventHandler ResourcesChanged; + + /// + /// Gets the loaded resource dictionary. + /// + public IResourceDictionary Loaded + { + get + { + if (_loaded == null) + { + var loader = new AvaloniaXamlLoader(); + _loaded = (IResourceDictionary)loader.Load(Source, _baseUri); + + if (_loaded.HasResources) + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + } + + return _loaded; + } + } + + /// + /// Gets or sets the source URL. + /// + public Uri Source { get; set; } + + /// + bool IResourceProvider.HasResources => Loaded.HasResources; + + /// + bool IResourceProvider.TryGetResource(string key, out object value) + { + return Loaded.TryGetResource(key, out value); + } + + /// + public override object ProvideValue(IServiceProvider serviceProvider) + { + var tdc = (ITypeDescriptorContext)serviceProvider; + _baseUri = tdc?.GetBaseUri(); + return this; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index c1867da9c0..fb308e62b8 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -52,7 +52,7 @@ namespace Avalonia.Markup.Xaml.Styling } /// - bool IResourceNode.HasResources => Loaded.HasResources; + bool IResourceProvider.HasResources => Loaded.HasResources; /// IResourceNode IResourceNode.ResourceParent => _parent; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs new file mode 100644 index 0000000000..6dc56e425c --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs @@ -0,0 +1,55 @@ +using System; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Data +{ + public class ResourceIncludeTests + { + public class StaticResourceExtensionTests + { + [Fact] + public void ResourceInclude_Loads_ResourceDictionary() + { + var includeXaml = @" + + #ff506070 + +"; + using (StartWithResources(("test:include.xaml", includeXaml))) + { + var xaml = @" + + + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + } + + private IDisposable StartWithResources(params (string, string)[] assets) + { + var assetLoader = new MockAssetLoader(assets); + var services = new TestServices(assetLoader: assetLoader); + return UnitTestApplication.Start(services); + } + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index c751f9e056..d55e34cbe6 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -485,7 +485,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Null(border.Background); - userControl.Resources.MergedDictionaries[0].Add("brush", new SolidColorBrush(0xff506070)); + ((IResourceDictionary)userControl.Resources.MergedDictionaries[0]).Add("brush", new SolidColorBrush(0xff506070)); var brush = (SolidColorBrush)border.Background; Assert.NotNull(brush); diff --git a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs index f31cbbac0a..1eb3cd9750 100644 --- a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs @@ -167,7 +167,7 @@ namespace Avalonia.Styling.UnitTests var raised = false; target.ResourcesChanged += (_, __) => raised = true; - target.MergedDictionaries[0].Add("foo", "bar"); + ((IResourceDictionary)target.MergedDictionaries[0]).Add("foo", "bar"); Assert.True(raised); } From e54f48b63ce7335472eb3626174991c37c0272f9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 19:14:32 +0200 Subject: [PATCH 047/110] React to application resources changing. --- src/Avalonia.Controls/TopLevel.cs | 24 +++++++++++++++++-- .../TopLevelTests.cs | 17 +++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index f8db0e2a5b..1af347ab4e 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -10,9 +10,11 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.Logging; +using Avalonia.LogicalTree; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; +using Avalonia.Utilities; using Avalonia.VisualTree; using JetBrains.Annotations; @@ -26,7 +28,13 @@ namespace Avalonia.Controls /// It handles scheduling layout, styling and rendering as well as /// tracking the widget's . /// - public abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable, IStyleRoot + public abstract class TopLevel : ContentControl, + IInputRoot, + ILayoutRoot, + IRenderRoot, + ICloseable, + IStyleRoot, + IWeakSubscriber { /// /// Defines the property. @@ -100,7 +108,6 @@ namespace Avalonia.Controls impl.Resized = HandleResized; impl.ScalingChanged = HandleScalingChanged; - _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); styler?.ApplyStyles(this); @@ -116,6 +123,14 @@ namespace Avalonia.Controls { _applicationLifecycle.OnExit += OnApplicationExiting; } + + if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources) + { + WeakSubscriptionManager.Subscribe( + applicationResources, + nameof(IResourceProvider.ResourcesChanged), + this); + } } /// @@ -165,6 +180,11 @@ namespace Avalonia.Controls /// IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice; + void IWeakSubscriber.OnEvent(object sender, ResourcesChangedEventArgs e) + { + ((ILogical)this).NotifyResourcesChanged(e); + } + /// /// Gets or sets a value indicating whether access keys are shown in the window. /// diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index da30336be6..da0719893f 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -219,6 +219,23 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Adding_Resource_To_Application_Should_Raise_ResourcesChanged() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var impl = new Mock(); + impl.SetupAllProperties(); + var target = new TestTopLevel(impl.Object); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + Application.Current.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + } + private FuncControlTemplate CreateTemplate() { return new FuncControlTemplate(x => From c47e44192bf9626f8ef3ab597ee388dd2e19638c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 19:56:24 +0200 Subject: [PATCH 048/110] Make resource dictionary keys objects. For parity with other XAML frameworks. --- src/Avalonia.Styling/Controls/IResourceDictionary.cs | 2 +- src/Avalonia.Styling/Controls/ResourceDictionary.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs index e6da375544..d7c86b7d74 100644 --- a/src/Avalonia.Styling/Controls/IResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs @@ -9,7 +9,7 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public interface IResourceDictionary : IResourceProvider, IDictionary + public interface IResourceDictionary : IResourceProvider, IDictionary { /// /// Gets a collection of child resource dictionaries. diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index ec0a59dad9..74a861b36b 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary + public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary { private AvaloniaList _mergedDictionaries; From c244ac809dd423290affdde201bd57cf1431b021 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 21:13:47 +0200 Subject: [PATCH 049/110] Fixed malformed doc comment. --- src/Avalonia.Styling/Controls/IResourceProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index 2ca83ea2d2..eec783623c 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -23,7 +23,8 @@ namespace Avalonia.Controls /// The resource key. /// /// When this method returns, contains the value associated with the specified key, - /// if the key is found; otherwise, null + /// if the key is found; otherwise, null. + /// /// /// True if the resource if found, otherwise false. /// From fe957d8093276e45c46a34dcd616fda5e91fe525 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 23:27:34 +0200 Subject: [PATCH 050/110] Fix compile error. --- src/Avalonia.Base/Collections/AvaloniaDictionary.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index 9f2a271092..724442c5f2 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -62,8 +62,6 @@ namespace Avalonia.Collections public object SyncRoot => ((IDictionary)_inner).SyncRoot; - public object this[object key] { get => ((IDictionary)_inner)[key]; set => ((IDictionary)_inner)[key] = value; } - /// /// Gets or sets the named resource. /// @@ -102,6 +100,8 @@ namespace Avalonia.Collections } } + object IDictionary.this[object key] { get => ((IDictionary)_inner)[key]; set => ((IDictionary)_inner)[key] = value; } + /// public void Add(TKey key, TValue value) { From f6ed69be28c162936a4152066bb3fea1436edffa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 20:08:38 +0200 Subject: [PATCH 051/110] StyleResource -> DynamicResource --- samples/ControlCatalog/Pages/BorderPage.xaml | 10 ++++----- samples/ControlCatalog/Pages/ButtonPage.xaml | 8 +++---- .../ControlCatalog/Pages/ContextMenuPage.xaml | 2 +- .../ControlCatalog/Pages/DropDownPage.xaml | 2 +- .../Pages/LayoutTransformControlPage.xaml | 8 +++---- samples/ControlCatalog/Pages/ToolTipPage.xaml | 2 +- samples/ControlCatalog/SideBar.xaml | 4 ++-- samples/RenderTest/Pages/ClippingPage.xaml | 2 +- samples/RenderTest/Pages/DrawingPage.xaml | 8 +++---- samples/RenderTest/SideBar.xaml | 4 ++-- src/Avalonia.Themes.Default/Button.xaml | 14 ++++++------ src/Avalonia.Themes.Default/CheckBox.xaml | 10 ++++----- src/Avalonia.Themes.Default/DropDown.xaml | 10 ++++----- .../EmbeddableControlRoot.xaml | 4 ++-- src/Avalonia.Themes.Default/GridSplitter.xaml | 8 +++---- src/Avalonia.Themes.Default/ListBox.xaml | 6 ++--- src/Avalonia.Themes.Default/MenuItem.xaml | 18 +++++++-------- src/Avalonia.Themes.Default/PopupRoot.xaml | 2 +- src/Avalonia.Themes.Default/ProgressBar.xaml | 4 ++-- src/Avalonia.Themes.Default/RadioButton.xaml | 10 ++++----- src/Avalonia.Themes.Default/ScrollBar.xaml | 4 ++-- src/Avalonia.Themes.Default/Separator.xaml | 4 ++-- src/Avalonia.Themes.Default/Slider.xaml | 6 ++--- src/Avalonia.Themes.Default/TabStripItem.xaml | 6 ++--- src/Avalonia.Themes.Default/TextBox.xaml | 22 +++++++++---------- src/Avalonia.Themes.Default/ToggleButton.xaml | 18 +++++++-------- src/Avalonia.Themes.Default/ToolTip.xaml | 6 ++--- src/Avalonia.Themes.Default/TreeView.xaml | 4 ++-- src/Avalonia.Themes.Default/TreeViewItem.xaml | 2 +- src/Avalonia.Themes.Default/Window.xaml | 4 ++-- .../Xaml/Style2.xaml | 6 ++--- 31 files changed, 109 insertions(+), 109 deletions(-) diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml index 9842745413..a81bd13ddd 100644 --- a/samples/ControlCatalog/Pages/BorderPage.xaml +++ b/samples/ControlCatalog/Pages/BorderPage.xaml @@ -7,22 +7,22 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Gap="16"> - + Border - Border and Background - Rounded Corners - Rounded Corners diff --git a/samples/ControlCatalog/Pages/ButtonPage.xaml b/samples/ControlCatalog/Pages/ButtonPage.xaml index e595f27263..b5335ff15e 100644 --- a/samples/ControlCatalog/Pages/ButtonPage.xaml +++ b/samples/ControlCatalog/Pages/ButtonPage.xaml @@ -11,7 +11,7 @@ - + - - - + + + diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml b/samples/ControlCatalog/Pages/ContextMenuPage.xaml index f029b1106f..3af823befc 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml @@ -7,7 +7,7 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Gap="16"> - diff --git a/samples/ControlCatalog/Pages/DropDownPage.xaml b/samples/ControlCatalog/Pages/DropDownPage.xaml index b392770060..0a7a88e331 100644 --- a/samples/ControlCatalog/Pages/DropDownPage.xaml +++ b/samples/ControlCatalog/Pages/DropDownPage.xaml @@ -14,7 +14,7 @@ - + Control Items diff --git a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml index 850eab8818..b428cd1b9f 100644 --- a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml +++ b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml @@ -10,10 +10,10 @@ RowDefinitions="24,Auto,24" HorizontalAlignment="Center" VerticalAlignment="Center"> - - - - + + + + diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml index 29df11510c..0c55fa28e4 100644 --- a/samples/ControlCatalog/Pages/ToolTipPage.xaml +++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml @@ -7,7 +7,7 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Gap="16"> - diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 950de71e2b..c03bc432d4 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -3,7 +3,7 @@ - + diff --git a/samples/RenderTest/Pages/ClippingPage.xaml b/samples/RenderTest/Pages/ClippingPage.xaml index 47e9d51e3c..238e3449de 100644 --- a/samples/RenderTest/Pages/ClippingPage.xaml +++ b/samples/RenderTest/Pages/ClippingPage.xaml @@ -5,7 +5,7 @@ Width="100" Height="100" Clip="M 58.625 0.07421875 C 50.305778 0.26687364 42.411858 7.0346526 41.806641 15.595703 C 42.446442 22.063923 39.707425 13.710754 36.982422 12.683594 C 29.348395 6.1821635 16.419398 8.4359222 11.480469 17.195312 C 6.0935256 25.476803 9.8118851 37.71125 18.8125 41.6875 C 9.1554771 40.62945 -0.070876925 49.146842 0.21679688 58.857422 C 0.21545578 60.872512 0.56758794 62.88911 1.2617188 64.78125 C 4.3821886 74.16708 16.298268 78.921772 25.03125 74.326172 C 28.266843 72.062552 26.298191 74.214838 25.414062 76.398438 C 21.407348 85.589198 27.295992 97.294293 37.097656 99.501953 C 46.864883 102.3541 57.82177 94.726518 58.539062 84.580078 C 58.142158 79.498998 59.307538 83.392694 61.207031 85.433594 C 67.532324 93.056874 80.440232 93.192029 86.882812 85.630859 C 93.836392 78.456939 92.396838 65.538666 84.115234 60.009766 C 79.783641 57.904836 83.569793 58.802369 86.375 58.193359 C 96.383335 56.457569 102.87506 44.824101 99.083984 35.394531 C 95.963498 26.008711 84.047451 21.254079 75.314453 25.849609 C 72.078834 28.113269 74.047517 25.960974 74.931641 23.777344 C 78.93827 14.586564 73.049722 2.8815081 63.248047 0.67382812 C 61.721916 0.22817968 60.165597 0.038541919 58.625 0.07421875 z "> - + diff --git a/samples/RenderTest/Pages/DrawingPage.xaml b/samples/RenderTest/Pages/DrawingPage.xaml index 81181e01fc..ee892774f0 100644 --- a/samples/RenderTest/Pages/DrawingPage.xaml +++ b/samples/RenderTest/Pages/DrawingPage.xaml @@ -49,7 +49,7 @@ BorderThickness="1" BorderBrush="Gray" Margin="5"> - + - @@ -76,7 +76,7 @@ BorderThickness="1" BorderBrush="Gray" Margin="5"> - @@ -91,7 +91,7 @@ BorderThickness="1" BorderBrush="Gray" Margin="5"> - diff --git a/samples/RenderTest/SideBar.xaml b/samples/RenderTest/SideBar.xaml index 950de71e2b..c03bc432d4 100644 --- a/samples/RenderTest/SideBar.xaml +++ b/samples/RenderTest/SideBar.xaml @@ -3,7 +3,7 @@ - + diff --git a/src/Avalonia.Themes.Default/Button.xaml b/src/Avalonia.Themes.Default/Button.xaml index daa2973b21..908f293fa7 100644 --- a/src/Avalonia.Themes.Default/Button.xaml +++ b/src/Avalonia.Themes.Default/Button.xaml @@ -1,9 +1,9 @@ \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/CheckBox.xaml b/src/Avalonia.Themes.Default/CheckBox.xaml index 2fc19b2679..195ec098c9 100644 --- a/src/Avalonia.Themes.Default/CheckBox.xaml +++ b/src/Avalonia.Themes.Default/CheckBox.xaml @@ -1,8 +1,8 @@ \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 5a3d44360c..0e8b409cfd 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml index edea0ddf64..f39720bb65 100644 --- a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml +++ b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml @@ -1,7 +1,7 @@ @@ -25,7 +25,7 @@ diff --git a/src/Avalonia.Themes.Default/ListBox.xaml b/src/Avalonia.Themes.Default/ListBox.xaml index 5aa23f5275..aa63a1b6c3 100644 --- a/src/Avalonia.Themes.Default/ListBox.xaml +++ b/src/Avalonia.Themes.Default/ListBox.xaml @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index 89f6f26c08..1520e12962 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -2,7 +2,7 @@ diff --git a/src/Avalonia.Themes.Default/Slider.xaml b/src/Avalonia.Themes.Default/Slider.xaml index 4fa293fbc5..b6f3cff318 100644 --- a/src/Avalonia.Themes.Default/Slider.xaml +++ b/src/Avalonia.Themes.Default/Slider.xaml @@ -16,7 +16,7 @@ - + @@ -43,7 +43,7 @@ - + @@ -61,6 +61,6 @@ \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/TabStripItem.xaml b/src/Avalonia.Themes.Default/TabStripItem.xaml index cee1c5e460..7f7fef0a1a 100644 --- a/src/Avalonia.Themes.Default/TabStripItem.xaml +++ b/src/Avalonia.Themes.Default/TabStripItem.xaml @@ -1,8 +1,8 @@ \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 8a5a41845b..8acc97d663 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -1,8 +1,8 @@ \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/ToggleButton.xaml b/src/Avalonia.Themes.Default/ToggleButton.xaml index 0c8a3fa425..12d7daacda 100644 --- a/src/Avalonia.Themes.Default/ToggleButton.xaml +++ b/src/Avalonia.Themes.Default/ToggleButton.xaml @@ -1,9 +1,9 @@ \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/ToolTip.xaml b/src/Avalonia.Themes.Default/ToolTip.xaml index 46e297fbc2..f3e819c101 100644 --- a/src/Avalonia.Themes.Default/ToolTip.xaml +++ b/src/Avalonia.Themes.Default/ToolTip.xaml @@ -1,7 +1,7 @@ \ No newline at end of file From a9748ee901456432a25fc7e93011b58eeeeca850 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Aug 2017 00:36:06 +0200 Subject: [PATCH 052/110] Removed StyleResource from docs. --- docs/tutorial/from-wpf.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/tutorial/from-wpf.md b/docs/tutorial/from-wpf.md index 0eb6e80327..d244fb076b 100644 --- a/docs/tutorial/from-wpf.md +++ b/docs/tutorial/from-wpf.md @@ -73,17 +73,6 @@ and includes `DirectProperty` for turning standard CLR properties into Avalonia properties. The common base class of `StyledProperty` and `DirectProperty` is `AvaloniaProperty`. -# Resources - -There is no `Resources` collection on controls in Avalonia, however `Style`s -do have a `Resources` collection for style-related resources. These can be -referred to using the `{StyleResource}` markup extension both inside and outside -styles. - -For non-style-related resources, we suggest defining them in code and referring -to them in markup using the `{Static}` markup extension. To read more about the reasoning for this, -see [this issue comment](https://github.com/AvaloniaUI/Avalonia/issues/462#issuecomment-191849723). - ## Grid Column and row definitions can be specified in Avalonia using strings, avoiding From 8c9dd5c81e7100df3a4de8e579da05ebf42f2440 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Aug 2017 01:06:28 +0200 Subject: [PATCH 053/110] Update moq to 4.7.99. This fixes a bunch of build warnings. --- build/Moq.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Moq.props b/build/Moq.props index 55242d922e..7de9b6b6ba 100644 --- a/build/Moq.props +++ b/build/Moq.props @@ -1,5 +1,5 @@  - + From 668de008ff23a4eee94190a79d7e1ddaccd3e9c4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 27 Aug 2017 02:42:30 +0300 Subject: [PATCH 054/110] Fixed Utf8Buffer and downgraded JetBrains.ReSharper.CommandLineTools --- build.cake | 2 +- src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 45f2d9e18d..4e18f23780 100644 --- a/build.cake +++ b/build.cake @@ -6,7 +6,7 @@ #addin "nuget:?package=NuGet.Core&version=2.12.0" #tool "nuget:?package=xunit.runner.console&version=2.2.0" #tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-preview1-3980&prerelease" -#tool "JetBrains.ReSharper.CommandLineTools" +#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720" /////////////////////////////////////////////////////////////////////////////// // TOOLS /////////////////////////////////////////////////////////////////////////////// diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs b/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs index fc76fefd1a..c932aac950 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs @@ -36,6 +36,8 @@ namespace Avalonia.Gtk3.Interop public static unsafe string StringFromPtr(IntPtr s) { var pstr = (byte*)s; + if (pstr == null) + return null; int len; for (len = 0; pstr[len] != 0; len++) ; var bytes = new byte[len]; From 3b030f19d1ed47c344845581d5e5bcb1d24da6ce Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Aug 2017 02:19:56 +0200 Subject: [PATCH 055/110] Fix Avalonia.Input.UnitTests.csproj Previously this project was spewing loads of ``` Severity Code Description Project File Line Suppression State Warning CS1701 Assuming assembly reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' used by 'Avalonia.Controls' matches identity 'System.Runtime, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime', you may need to supply runtime policy Avalonia.Input.UnitTests(net461) D:\projects\Avalonia\tests\Avalonia.Input.UnitTests\CSC 1 Active` ``` Warnings. Not sure why this was, but updating the `csproj` to be in the same format as the other unit tests stops these warnings and reduces the warnings for a solution build from 5755(!) to 238. --- .../Avalonia.Input.UnitTests.csproj | 33 ++----------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj index 186d293b96..64d0efe69b 100644 --- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj +++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj @@ -3,35 +3,6 @@ net461;netcoreapp1.1 Library - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Input.UnitTests.XML - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - @@ -49,6 +20,6 @@ - + - \ No newline at end of file + From 5827f3c28428ab310745f79efca14eaa14bfa8e8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 27 Aug 2017 16:22:34 +0200 Subject: [PATCH 056/110] Missed file from merge --- tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj index 52404e7ed2..99cf4e98d2 100644 --- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj +++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj @@ -19,8 +19,7 @@ - - + \ No newline at end of file From dff3031e201ae5acba9494f0403cbbc0b9ffce3f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 5 Aug 2017 18:40:15 -0700 Subject: [PATCH 057/110] Upgrade ReactiveUI to the v8 alpha nuget package so we don't need to maintain our own fork. Avalonia.ReactiveUI only registers the MainScheduler, and it does so via a call to UseReactiveUI on AppBuilder. Update Rx.Net dependency to match reactiveui's dependency on Rx. Removed ReactiveUI dependeny in BindingTests. Updated BindingTest sample to use the new reactiveui APIs. Update samples to use current ReactiveUI APIs. Fixed bug I introduced into tests. Remove RxUI submodule info from .gitmodules --- .gitmodules | 4 -- Avalonia.sln | 3 +- build/ReactiveUI.props | 5 ++ build/Rx.props | 14 +++--- samples/BindingTest/App.xaml.cs | 1 + samples/BindingTest/BindingTest.csproj | 2 +- .../ViewModels/MainWindowViewModel.cs | 10 ++-- samples/RenderTest/Program.cs | 1 + samples/RenderTest/RenderTest.csproj | 1 + .../ViewModels/MainWindowViewModel.cs | 10 ++-- samples/VirtualizationTest/Program.cs | 1 + .../ViewModels/MainWindowViewModel.cs | 25 ++++------ .../VirtualizationTest.csproj | 1 + .../AppBuilderExtensions.cs | 24 +++++++++ .../Avalonia.ReactiveUI.csproj | 50 +------------------ .../Properties/AssemblyInfo.cs | 3 -- src/Avalonia.ReactiveUI/Registrations.cs | 23 --------- src/Avalonia.ReactiveUI/Shims.cs | 34 ------------- src/Avalonia.ReactiveUI/src | 1 - .../Avalonia.Markup.Xaml.UnitTests.csproj | 1 - .../Data/BindingTests.cs | 18 +++++-- .../Data/BindingTests_Source.cs | 20 ++++++-- 22 files changed, 95 insertions(+), 157 deletions(-) create mode 100644 build/ReactiveUI.props create mode 100644 src/Avalonia.ReactiveUI/AppBuilderExtensions.cs delete mode 100644 src/Avalonia.ReactiveUI/Registrations.cs delete mode 100644 src/Avalonia.ReactiveUI/Shims.cs delete mode 160000 src/Avalonia.ReactiveUI/src diff --git a/.gitmodules b/.gitmodules index f446fe421e..98b6d076c1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,3 @@ -[submodule "src/Avalonia.ReactiveUI/src"] - path = src/Avalonia.ReactiveUI/src - url = https://github.com/AvaloniaUI/ReactiveUI.git - branch = avalonia-snapshot [submodule "src/Avalonia.HtmlRenderer/external"] path = src/Avalonia.HtmlRenderer/external url = https://github.com/AvaloniaUI/HTML-Renderer.git diff --git a/Avalonia.sln b/Avalonia.sln index 391d02382e..39d44cb172 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26430.16 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}" EndProject @@ -159,6 +159,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props build\Moq.props = build\Moq.props build\NetCore.props = build\NetCore.props + build\ReactiveUI.props = build\ReactiveUI.props build\Rx.props = build\Rx.props build\Serilog.props = build\Serilog.props build\Serilog.Sinks.Trace.props = build\Serilog.Sinks.Trace.props diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props new file mode 100644 index 0000000000..4473447647 --- /dev/null +++ b/build/ReactiveUI.props @@ -0,0 +1,5 @@ + + + + + diff --git a/build/Rx.props b/build/Rx.props index 5d74a6b214..e63055da3e 100644 --- a/build/Rx.props +++ b/build/Rx.props @@ -1,11 +1,11 @@  - - - - - - - + + + + + + + diff --git a/samples/BindingTest/App.xaml.cs b/samples/BindingTest/App.xaml.cs index 7bcaf837a8..42e5716541 100644 --- a/samples/BindingTest/App.xaml.cs +++ b/samples/BindingTest/App.xaml.cs @@ -20,6 +20,7 @@ namespace BindingTest AppBuilder.Configure() .UsePlatformDetect() + .UseReactiveUI() .Start(); } diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index eab656eb33..b4dd17194b 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -45,7 +45,6 @@ - @@ -163,4 +162,5 @@ + \ No newline at end of file diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index d8ea280abf..1116810ccb 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -28,15 +28,13 @@ namespace BindingTest.ViewModels SelectedItems = new ObservableCollection(); - ShuffleItems = ReactiveCommand.Create(); - ShuffleItems.Subscribe(_ => + ShuffleItems = ReactiveCommand.Create(() => { var r = new Random(); Items.Move(r.Next(Items.Count), 1); }); - StringValueCommand = ReactiveCommand.Create(); - StringValueCommand.Subscribe(param => + StringValueCommand = ReactiveCommand.Create(param => { BooleanFlag = !BooleanFlag; StringValue = param.ToString(); @@ -58,7 +56,7 @@ namespace BindingTest.ViewModels public ObservableCollection Items { get; } public ObservableCollection SelectedItems { get; } - public ReactiveCommand ShuffleItems { get; } + public ReactiveCommand ShuffleItems { get; } public string BooleanString { @@ -91,7 +89,7 @@ namespace BindingTest.ViewModels } public IObservable CurrentTimeObservable { get; } - public ReactiveCommand StringValueCommand { get; } + public ReactiveCommand StringValueCommand { get; } public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel(); public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel(); diff --git a/samples/RenderTest/Program.cs b/samples/RenderTest/Program.cs index 7a23e09dd4..514771097f 100644 --- a/samples/RenderTest/Program.cs +++ b/samples/RenderTest/Program.cs @@ -18,6 +18,7 @@ namespace RenderTest // again. AppBuilder.Configure() .UsePlatformDetect() + .UseReactiveUI() .Start(); } diff --git a/samples/RenderTest/RenderTest.csproj b/samples/RenderTest/RenderTest.csproj index b7e64f4dae..974b8802ac 100644 --- a/samples/RenderTest/RenderTest.csproj +++ b/samples/RenderTest/RenderTest.csproj @@ -191,4 +191,5 @@ + \ No newline at end of file diff --git a/samples/RenderTest/ViewModels/MainWindowViewModel.cs b/samples/RenderTest/ViewModels/MainWindowViewModel.cs index bd68752f66..b2fa2e8b7a 100644 --- a/samples/RenderTest/ViewModels/MainWindowViewModel.cs +++ b/samples/RenderTest/ViewModels/MainWindowViewModel.cs @@ -10,10 +10,8 @@ namespace RenderTest.ViewModels public MainWindowViewModel() { - ToggleDrawDirtyRects = ReactiveCommand.Create(); - ToggleDrawDirtyRects.Subscribe(_ => DrawDirtyRects = !DrawDirtyRects); - ToggleDrawFps = ReactiveCommand.Create(); - ToggleDrawFps.Subscribe(_ => DrawFps = !DrawFps); + ToggleDrawDirtyRects = ReactiveCommand.Create(() => DrawDirtyRects = !DrawDirtyRects); + ToggleDrawFps = ReactiveCommand.Create(() => DrawFps = !DrawFps); } public bool DrawDirtyRects @@ -28,7 +26,7 @@ namespace RenderTest.ViewModels set { this.RaiseAndSetIfChanged(ref drawFps, value); } } - public ReactiveCommand ToggleDrawDirtyRects { get; } - public ReactiveCommand ToggleDrawFps { get; } + public ReactiveCommand ToggleDrawDirtyRects { get; } + public ReactiveCommand ToggleDrawFps { get; } } } diff --git a/samples/VirtualizationTest/Program.cs b/samples/VirtualizationTest/Program.cs index d5649f6a06..e9156d803f 100644 --- a/samples/VirtualizationTest/Program.cs +++ b/samples/VirtualizationTest/Program.cs @@ -17,6 +17,7 @@ namespace VirtualizationTest AppBuilder.Configure() .UsePlatformDetect() + .UseReactiveUI() .Start(); } diff --git a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs index 86869eb46a..a0e1570a36 100644 --- a/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs @@ -23,20 +23,15 @@ namespace VirtualizationTest.ViewModels public MainWindowViewModel() { this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems); - RecreateCommand = ReactiveCommand.Create(); - RecreateCommand.Subscribe(_ => Recreate()); + RecreateCommand = ReactiveCommand.Create(() => Recreate()); - AddItemCommand = ReactiveCommand.Create(); - AddItemCommand.Subscribe(_ => AddItem()); + AddItemCommand = ReactiveCommand.Create(() => AddItem()); - RemoveItemCommand = ReactiveCommand.Create(); - RemoveItemCommand.Subscribe(_ => Remove()); + RemoveItemCommand = ReactiveCommand.Create(() => Remove()); - SelectFirstCommand = ReactiveCommand.Create(); - SelectFirstCommand.Subscribe(_ => SelectItem(0)); + SelectFirstCommand = ReactiveCommand.Create(() => SelectItem(0)); - SelectLastCommand = ReactiveCommand.Create(); - SelectLastCommand.Subscribe(_ => SelectItem(Items.Count - 1)); + SelectLastCommand = ReactiveCommand.Create(() => SelectItem(Items.Count - 1)); } public string NewItemString @@ -78,11 +73,11 @@ namespace VirtualizationTest.ViewModels public IEnumerable VirtualizationModes => Enum.GetValues(typeof(ItemVirtualizationMode)).Cast(); - public ReactiveCommand AddItemCommand { get; private set; } - public ReactiveCommand RecreateCommand { get; private set; } - public ReactiveCommand RemoveItemCommand { get; private set; } - public ReactiveCommand SelectFirstCommand { get; private set; } - public ReactiveCommand SelectLastCommand { get; private set; } + public ReactiveCommand AddItemCommand { get; private set; } + public ReactiveCommand RecreateCommand { get; private set; } + public ReactiveCommand RemoveItemCommand { get; private set; } + public ReactiveCommand SelectFirstCommand { get; private set; } + public ReactiveCommand SelectLastCommand { get; private set; } private void ResizeItems(int count) { diff --git a/samples/VirtualizationTest/VirtualizationTest.csproj b/samples/VirtualizationTest/VirtualizationTest.csproj index 327e659966..9d31a6cb86 100644 --- a/samples/VirtualizationTest/VirtualizationTest.csproj +++ b/samples/VirtualizationTest/VirtualizationTest.csproj @@ -158,4 +158,5 @@ + \ No newline at end of file diff --git a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs new file mode 100644 index 0000000000..e081763e99 --- /dev/null +++ b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs @@ -0,0 +1,24 @@ +// 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.Controls; +using Avalonia.Threading; +using ReactiveUI; +using System; +using System.Reactive.Concurrency; +using System.Threading; + +namespace Avalonia +{ + public static class AppBuilderExtensions + { + public static TAppBuilder UseReactiveUI(this TAppBuilder builder) + where TAppBuilder : AppBuilderBase, new() + { + return builder.AfterSetup(_ => + { + RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; + }); + } + } +} diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 22d815d786..d523f71416 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -1,7 +1,6 @@  netstandard1.3 - False false @@ -29,53 +28,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -88,5 +41,6 @@ + \ No newline at end of file diff --git a/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs b/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs index c8a5c5cc41..c9ead6f6e6 100644 --- a/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs +++ b/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs @@ -3,7 +3,4 @@ using System.Reflection; -// 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("Avalonia.ReactiveUI")] diff --git a/src/Avalonia.ReactiveUI/Registrations.cs b/src/Avalonia.ReactiveUI/Registrations.cs deleted file mode 100644 index 066aa46dc1..0000000000 --- a/src/Avalonia.ReactiveUI/Registrations.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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.Reactive.Concurrency; -using System.Threading; - - -namespace ReactiveUI -{ - /// - /// Ignore me. This class is a secret handshake between RxUI and RxUI.Xaml - /// in order to register certain classes on startup that would be difficult - /// to register otherwise. - /// - public class PlatformRegistrations : IWantsToRegisterStuff - { - public void Register(Action, Type> registerFunction) - { - RxApp.MainThreadScheduler = new SynchronizationContextScheduler(SynchronizationContext.Current); - } - } -} diff --git a/src/Avalonia.ReactiveUI/Shims.cs b/src/Avalonia.ReactiveUI/Shims.cs deleted file mode 100644 index 2729a68d6f..0000000000 --- a/src/Avalonia.ReactiveUI/Shims.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace System.Runtime.Serialization -{ - class IgnoreDataMemberAttribute : Attribute - { - } - - class DataMemberAttribute : Attribute - { - } - class OnDeserializedAttribute : Attribute - { - } - - class DataContractAttribute : Attribute - { - } - - class StreamingContext { } -} - -namespace System.Diagnostics.Contracts -{ - static class Contract - { - public static void Requires(bool condition) - { - - } - } -} diff --git a/src/Avalonia.ReactiveUI/src b/src/Avalonia.ReactiveUI/src deleted file mode 160000 index 3f725c808b..0000000000 --- a/src/Avalonia.ReactiveUI/src +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3f725c808b1d4c8457f0d3204e0a071aa462cd75 diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index f6f8f6bcb0..48f8c6f396 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -19,7 +19,6 @@ - diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs index bd86877404..230e61f300 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -10,8 +10,9 @@ using Avalonia.Data; using Avalonia.Markup.Data; using Avalonia.Markup.Xaml.Data; using Moq; -using ReactiveUI; using Xunit; +using System.ComponentModel; +using System.Runtime.CompilerServices; namespace Avalonia.Markup.Xaml.UnitTests.Data { @@ -350,14 +351,25 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data } } - public class Source : ReactiveObject + public class Source : INotifyPropertyChanged { private string _foo; public string Foo { get { return _foo; } - set { this.RaiseAndSetIfChanged(ref _foo, value); } + set + { + _foo = value; + RaisePropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void RaisePropertyChanged([CallerMemberName] string prop = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Source.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Source.cs index 778a93d326..c6006f3afb 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Source.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Source.cs @@ -6,8 +6,9 @@ using Avalonia.Controls; using Avalonia.Data; using Avalonia.Markup.Data; using Avalonia.Markup.Xaml.Data; -using ReactiveUI; using Xunit; +using System.ComponentModel; +using System.Runtime.CompilerServices; namespace Avalonia.Markup.Xaml.UnitTests.Data { @@ -24,15 +25,26 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data Assert.Equal(target.Text, "foo"); } - - public class Source : ReactiveObject + + public class Source : INotifyPropertyChanged { private string _foo; public string Foo { get { return _foo; } - set { this.RaiseAndSetIfChanged(ref _foo, value); } + set + { + _foo = value; + RaisePropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void RaisePropertyChanged([CallerMemberName] string prop = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); } } } From c1ad9b36d85339fa7d330799e0fffc8a0b7147b5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 5 Aug 2017 20:11:01 -0700 Subject: [PATCH 058/110] Create Avalonia.ReactiveUI package. PR feedback Update ReactiveUI version to version that supports .NET Core --- build/ReactiveUI.props | 2 +- build/Rx.props | 3 +-- packages.cake | 22 ++++++++++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props index 4473447647..de8d1d2104 100644 --- a/build/ReactiveUI.props +++ b/build/ReactiveUI.props @@ -1,5 +1,5 @@ - + diff --git a/build/Rx.props b/build/Rx.props index e63055da3e..e88ab1ec37 100644 --- a/build/Rx.props +++ b/build/Rx.props @@ -5,7 +5,6 @@ - - + diff --git a/packages.cake b/packages.cake index 22a519802f..8aa8216498 100644 --- a/packages.cake +++ b/packages.cake @@ -111,6 +111,7 @@ public class Packages var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1; var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1; var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1; + var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1; var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1; SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1; SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1; @@ -124,6 +125,7 @@ public class Packages context.Information("Package: Splat, version: {0}", SplatVersion); context.Information("Package: Sprache, version: {0}", SpracheVersion); context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion); + context.Information("Package: reactiveui, version: {0}", ReactiveUIVersion); context.Information("Package: System.ValueTuple, version: {0}", SystemValueTupleVersion); context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion); context.Information("Package: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion); @@ -176,7 +178,6 @@ public class Packages new [] { "./src/", "Avalonia.Visuals", ".xml" }, new [] { "./src/", "Avalonia.Styling", ".dll" }, new [] { "./src/", "Avalonia.Styling", ".xml" }, - new [] { "./src/", "Avalonia.ReactiveUI", ".dll" }, new [] { "./src/", "Avalonia.Themes.Default", ".dll" }, new [] { "./src/", "Avalonia.Themes.Default", ".xml" }, new [] { "./src/Markup/", "Avalonia.Markup", ".dll" }, @@ -273,7 +274,24 @@ public class Packages }, BasePath = context.Directory("./src/Avalonia.HtmlRenderer/bin/" + parameters.DirSuffix + "/netstandard1.3"), OutputDirectory = parameters.NugetRoot - } + }, + /////////////////////////////////////////////////////////////////////////////// + // Avalonia.ReactiveUI + /////////////////////////////////////////////////////////////////////////////// + new NuGetPackSettings() + { + Id = "Avalonia.ReactiveUI", + Dependencies = new DependencyBuilder(this) + { + new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }, + }.Deps(new string[] {null}, "reactiveui"), + Files = new [] + { + new NuSpecContent { Source = "Avalonia.ReactiveUI.dll", Target = "lib/netstandard1.3" } + }, + BasePath = context.Directory("./src/Avalonia.ReactiveUI/bin/" + parameters.DirSuffix + "/netstandard1.3"), + OutputDirectory = parameters.NugetRoot + }, }; var nuspecNuGetSettingsMobile = new [] From 106146460d05c72c320f8d8cbbd2fc026625de42 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Mon, 28 Aug 2017 19:26:53 +0300 Subject: [PATCH 059/110] Add RepeatButton --- src/Avalonia.Controls/Button.cs | 52 ++++-------- .../Primitives/ToggleButton.cs | 4 +- src/Avalonia.Controls/RepeatButton.cs | 82 +++++++++++++++++++ src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 + src/Avalonia.Themes.Default/RepeatButton.xaml | 44 ++++++++++ 5 files changed, 144 insertions(+), 39 deletions(-) create mode 100644 src/Avalonia.Controls/RepeatButton.cs create mode 100644 src/Avalonia.Themes.Default/RepeatButton.xaml diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 52329b9c88..5b98a46dd0 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -2,13 +2,10 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; using System.Windows.Input; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Rendering; -using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -68,7 +65,7 @@ namespace Avalonia.Controls /// Defines the event. /// public static readonly RoutedEvent ClickEvent = - RoutedEvent.Register("Click", RoutingStrategies.Bubble); + RoutedEvent.Register(nameof(Click), RoutingStrategies.Bubble); private ICommand _command; @@ -78,7 +75,6 @@ namespace Avalonia.Controls static Button() { FocusableProperty.OverrideDefaultValue(typeof(Button), true); - ClickEvent.AddClassHandler