diff --git a/samples/BindingTest/MainWindow.xaml b/samples/BindingTest/MainWindow.xaml index 3547e33181..4eb45e07c5 100644 --- a/samples/BindingTest/MainWindow.xaml +++ b/samples/BindingTest/MainWindow.xaml @@ -1,11 +1,15 @@ + xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' + xmlns:vm="clr-namespace:BindingTest.ViewModels" + xmlns:local="clr-namespace:BindingTest"> + + + @@ -40,6 +44,10 @@ + + diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index d3dbad679a..b2f6497caa 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -1,7 +1,7 @@  + Icon="resm:ControlCatalog.Assets.test_icon.ico" + xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False"> diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 1107d34b3e..377871f658 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -1,5 +1,5 @@ diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index f39beced1a..7029273a84 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -1,6 +1,6 @@  + xmlns:local="clr-namespace:ControlCatalog"> \ No newline at end of file diff --git a/samples/RenderTest/MainWindow.xaml b/samples/RenderTest/MainWindow.xaml index 9e9a600161..a47ef37ec6 100644 --- a/samples/RenderTest/MainWindow.xaml +++ b/samples/RenderTest/MainWindow.xaml @@ -1,6 +1,6 @@ + xmlns:pages="clr-namespace:RenderTest.Pages"> diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs index 0b683239fb..78f744cea0 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs @@ -36,7 +36,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform _clientSize = value; UpdateParams(); } - + + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IScreenImpl Screen { get; } public Point Position diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index eae8db5a14..ba30af60bf 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -43,5 +43,21 @@ namespace Avalonia.Platform /// The resource was not found. /// Stream Open(Uri uri, Uri baseUri = null); + + /// + /// Opens the resource with the requested URI and returns the resource string and the + /// assembly containing the resource. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// + /// The stream containing the resource contents together with the assembly. + /// + /// + /// The resource was not found. + /// + Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null); } } diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 0a01cf3df4..4f7ac82df7 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -55,7 +55,7 @@ namespace Avalonia.Platform /// Gets the platform window handle. /// IPlatformHandle Handle { get; } - + /// /// Gets the maximum size of a window on the system. /// @@ -65,7 +65,13 @@ namespace Avalonia.Platform /// Sets the client size of the toplevel. /// void Resize(Size clientSize); - + + /// + /// Minimum width of the window. + /// + /// + void SetMinMaxSize(Size minSize, Size maxSize); + /// /// Gets platform specific display information /// diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 1f84574318..3f2c977718 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -45,6 +45,11 @@ namespace Avalonia.Platform /// void ShowTaskbarIcon(bool value); + /// + /// Enables or disables resizing of the window + /// + void CanResize(bool value); + /// /// Gets or sets a method called before the underlying implementation is destroyed. /// Return true to prevent the underlying implementation from closing. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index c66209d3c6..16ee3a46b3 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -95,6 +95,9 @@ namespace Avalonia.Controls o => o.WindowStartupLocation, (o, v) => o.WindowStartupLocation = v); + public static readonly StyledProperty CanResizeProperty = + AvaloniaProperty.Register(nameof(CanResize), true); + private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; @@ -113,6 +116,8 @@ namespace Avalonia.Controls ShowInTaskbarProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue)); IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); + + CanResizeProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue)); } /// @@ -208,6 +213,15 @@ namespace Avalonia.Controls } } + /// + /// Enables or disables resizing of the window + /// + public bool CanResize + { + get { return GetValue(CanResizeProperty); } + set { SetValue(CanResizeProperty, value); } + } + /// /// Gets or sets the icon of the window. /// diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 50068d280d..c427df1c26 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -47,6 +47,11 @@ namespace Avalonia.Controls { IsVisibleProperty.OverrideDefaultValue(false); IsVisibleProperty.Changed.AddClassHandler(x => x.IsVisibleChanged); + + MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight))); + MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); + MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight))); + MaxHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue))); } public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current) diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index 5235beee0f..d1958ac9bf 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using Avalonia.Controls; using Avalonia.Controls.Platform; @@ -30,7 +31,8 @@ namespace Avalonia.DesignerSupport new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(assemblyPath)); } - var loaded = loader.Load(stream, null, baseUri); + var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null; + var loaded = loader.Load(stream, localAsm, null, baseUri); var styles = loaded as Styles; if (styles != null) { diff --git a/src/Avalonia.DesignerSupport/DesignerAssist.cs b/src/Avalonia.DesignerSupport/DesignerAssist.cs index 65c4c14d83..f6f704b838 100644 --- a/src/Avalonia.DesignerSupport/DesignerAssist.cs +++ b/src/Avalonia.DesignerSupport/DesignerAssist.cs @@ -24,7 +24,7 @@ namespace Avalonia.DesignerSupport var loader = new AvaloniaXamlLoader(); var baseLight = (IStyle)loader.Load( - new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")); + new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"), null); Styles.Add(baseLight); } } diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index fc9541abb7..9750b46aa2 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -67,6 +67,10 @@ namespace Avalonia.DesignerSupport.Remote RenderIfNeeded(); } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IScreenImpl Screen { get; } = new ScreenStub(); public void Activate() @@ -93,5 +97,9 @@ namespace Avalonia.DesignerSupport.Remote public void ShowTaskbarIcon(bool value) { } + + public void CanResize(bool value) + { + } } } \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 560425286e..ee8569d748 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -78,6 +78,10 @@ namespace Avalonia.DesignerSupport.Remote public IScreenImpl Screen { get; } = new ScreenStub(); + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public void SetTitle(string title) { } @@ -95,6 +99,10 @@ namespace Avalonia.DesignerSupport.Remote public void ShowTaskbarIcon(bool value) { } + + public void CanResize(bool value) + { + } } class ClipboardStub : IClipboard diff --git a/src/Avalonia.Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Views/TreePageView.xaml index 244afda135..a715ca6fc5 100644 --- a/src/Avalonia.Diagnostics/Views/TreePageView.xaml +++ b/src/Avalonia.Diagnostics/Views/TreePageView.xaml @@ -1,5 +1,5 @@ + xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"> diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index d4618e7bc1..1adaf9f4e1 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -115,6 +115,8 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_set_title(GtkWindow gtkWindow, Utf8Buffer title); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_set_resizable(GtkWindow gtkWindow, bool resizable); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_set_decorated(GtkWindow gtkWindow, bool decorated); @@ -263,7 +265,7 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_close(GtkWindow window); - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] @@ -395,6 +397,7 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_screen_get_monitor_geometry GdkScreenGetMonitorGeometry; public static D.gdk_screen_get_monitor_workarea GdkScreenGetMonitorWorkarea; public static D.gtk_window_set_decorated GtkWindowSetDecorated; + public static D.gtk_window_set_resizable GtkWindowSetResizable; public static D.gtk_window_set_skip_taskbar_hint GtkWindowSetSkipTaskbarHint; public static D.gtk_window_get_skip_taskbar_hint GtkWindowGetSkipTaskbarHint; public static D.gtk_window_set_skip_pager_hint GtkWindowSetSkipPagerHint; @@ -421,6 +424,7 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_window_set_override_redirect GdkWindowSetOverrideRedirect; public static D.gtk_widget_set_size_request GtkWindowSetSizeRequest; public static D.gtk_window_set_default_size GtkWindowSetDefaultSize; + public static D.gtk_window_set_geometry_hints GtkWindowSetGeometryHints; public static D.gtk_window_get_position GtkWindowGetPosition; public static D.gtk_window_move GtkWindowMove; public static D.gtk_file_chooser_dialog_new GtkFileChooserDialogNew; @@ -502,6 +506,7 @@ namespace Avalonia.Gtk3.Interop public static D.cairo_set_font_size CairoSetFontSize; public static D.cairo_move_to CairoMoveTo; public static D.cairo_destroy CairoDestroy; + public const int G_TYPE_OBJECT = 80; } @@ -739,19 +744,19 @@ namespace Avalonia.Gtk3.Interop } [StructLayout(LayoutKind.Sequential)] - struct GdkGeometry + public struct GdkGeometry { - gint min_width; - gint min_height; - gint max_width; - gint max_height; - gint base_width; - gint base_height; - gint width_inc; - gint height_inc; - gdouble min_aspect; - gdouble max_aspect; - gint win_gravity; + public gint min_width; + public gint min_height; + public gint max_width; + public gint max_height; + public gint base_width; + public gint base_height; + public gint width_inc; + public gint height_inc; + public gdouble min_aspect; + public gdouble max_aspect; + public gint win_gravity; } enum GdkWindowHints diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index a42c8a19b9..0ebfea998a 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -341,6 +341,20 @@ namespace Avalonia.Gtk3 } } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + if (GtkWidget.IsClosed) + return; + + GdkGeometry geometry = new GdkGeometry(); + geometry.min_width = minSize.Width > 0 ? (int)minSize.Width : -1; + geometry.min_height = minSize.Height > 0 ? (int)minSize.Height : -1; + geometry.max_width = !Double.IsInfinity(maxSize.Width) && maxSize.Width > 0 ? (int)maxSize.Width : 999999; + geometry.max_height = !Double.IsInfinity(maxSize.Height) && maxSize.Height > 0 ? (int)maxSize.Height : 999999; + + Native.GtkWindowSetGeometryHints(GtkWidget, IntPtr.Zero, ref geometry, GdkWindowHints.GDK_HINT_MIN_SIZE | GdkWindowHints.GDK_HINT_MAX_SIZE); + } + public IMouseDevice MouseDevice => Gtk3Platform.Mouse; public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1); @@ -431,6 +445,7 @@ namespace Avalonia.Gtk3 { if (GtkWidget.IsClosed) return; + Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height); if (OverrideRedirect) { diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index c586661a7a..2d309e19d4 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -61,6 +61,8 @@ namespace Avalonia.Gtk3 } public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value); + + public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value); class EmptyDisposable : IDisposable diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index bd6acfdad1..e388a49a6e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -29,7 +29,6 @@ Properties\SharedAssemblyInfo.cs - diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 3142d954ff..79523dd498 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -1,11 +1,237 @@ -namespace Avalonia.Markup.Xaml +// 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.Markup.Xaml.Data; +using Avalonia.Markup.Xaml.PortableXaml; +using Avalonia.Platform; +using Portable.Xaml; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; + +namespace Avalonia.Markup.Xaml { - public class AvaloniaXamlLoader : AvaloniaXamlLoaderPortableXaml + /// + /// Loads XAML for a avalonia application. + /// + public class AvaloniaXamlLoader { - public static object Parse(string xaml) - => new AvaloniaXamlLoader().Load(xaml); + private readonly AvaloniaXamlSchemaContext _context = GetContext(); + + private static AvaloniaXamlSchemaContext GetContext() + { + var result = AvaloniaLocator.Current.GetService(); + + if (result == null) + { + result = AvaloniaXamlSchemaContext.Create(); + + AvaloniaLocator.CurrentMutable + .Bind() + .ToConstant(result); + } + + return result; + } + + /// + /// Initializes a new instance of the class. + /// + public AvaloniaXamlLoader() + { + } + + /// + /// Loads the XAML into a Avalonia component. + /// + /// The object to load the XAML into. + public static void Load(object obj) + { + Contract.Requires(obj != null); + + var loader = new AvaloniaXamlLoader(); + loader.Load(obj.GetType(), obj); + } + + /// + /// Loads the XAML for a type. + /// + /// The type. + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(Type type, object rootInstance = null) + { + Contract.Requires(type != null); + + // HACK: Currently Visual Studio is forcing us to change the extension of xaml files + // in certain situations, so we try to load .xaml and if that's not found we try .xaml. + // Ideally we'd be able to use .xaml everywhere + var assetLocator = AvaloniaLocator.Current.GetService(); + + if (assetLocator == null) + { + throw new InvalidOperationException( + "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); + } + + foreach (var uri in GetUrisFor(type)) + { + if (assetLocator.Exists(uri)) + { + using (var stream = assetLocator.Open(uri)) + { + var initialize = rootInstance as ISupportInitialize; + initialize?.BeginInit(); + try + { + return Load(stream, type.Assembly, rootInstance, uri); + } + finally + { + initialize?.EndInit(); + } + } + } + } + + throw new FileNotFoundException("Unable to find view for " + type.FullName); + } + + /// + /// Loads XAML from a URI. + /// + /// The URI of the XAML file. + /// + /// A base URI to use if is relative. + /// + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) + { + Contract.Requires(uri != null); + + var assetLocator = AvaloniaLocator.Current.GetService(); + + if (assetLocator == null) + { + throw new InvalidOperationException( + "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); + } + + var asset = assetLocator.OpenAndGetAssembly(uri, baseUri); + using (var stream = asset.Item1) + { + try + { + return Load(stream, asset.Item2, rootInstance, uri); + } + catch (Exception e) + { + var uriString = uri.ToString(); + if (!uri.IsAbsoluteUri) + { + uriString = new Uri(baseUri, uri).AbsoluteUri; + } + throw new XamlLoadException("Error loading xaml at " + uriString, e); + } + } + } + + /// + /// Loads XAML from a string. + /// + /// The string containing the XAML. + /// Default assembly for clr-namespace: + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(string xaml, Assembly localAssembly = null, object rootInstance = null) + { + Contract.Requires(xaml != null); + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) + { + return Load(stream, localAssembly, rootInstance); + } + } + + /// + /// Loads XAML from a stream. + /// + /// The stream containing the XAML. + /// Default assembly for clr-namespace + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The URI of the XAML + /// The loaded object. + public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null) + { + var readerSettings = new XamlXmlReaderSettings() + { + BaseUri = uri, + LocalAssembly = localAssembly + }; + + var reader = new XamlXmlReader(stream, _context, readerSettings); + + object result = LoadFromReader( + reader, + AvaloniaXamlContext.For(readerSettings, rootInstance)); + + var topLevel = result as TopLevel; + + if (topLevel != null) + { + DelayedBinding.ApplyBindings(topLevel); + } + + return result; + } + + internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null, IAmbientProvider parentAmbientProvider = null) + { + var writer = AvaloniaXamlObjectWriter.Create( + reader.SchemaContext, + context, + parentAmbientProvider); + + XamlServices.Transform(reader, writer); + writer.ApplyAllDelayedProperties(); + return writer.Result; + } + + internal static object LoadFromReader(XamlReader reader) + { + //return XamlServices.Load(reader); + return LoadFromReader(reader, null); + } + + /// + /// Gets the URI for a type. + /// + /// The type. + /// The URI. + private static IEnumerable GetUrisFor(Type type) + { + var asm = type.GetTypeInfo().Assembly.GetName().Name; + var typeName = type.FullName; + yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm); + yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm); + } + + public static object Parse(string xaml, Assembly localAssembly = null) + => new AvaloniaXamlLoader().Load(xaml, localAssembly); - public static T Parse(string xaml) - => (T)Parse(xaml); + public static T Parse(string xaml, Assembly localAssembly = null) + => (T)Parse(xaml, localAssembly); } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs deleted file mode 100644 index de2a79c54e..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs +++ /dev/null @@ -1,227 +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 Avalonia.Controls; -using Avalonia.Markup.Xaml.Data; -using Avalonia.Markup.Xaml.PortableXaml; -using Avalonia.Platform; -using Portable.Xaml; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Text; - -namespace Avalonia.Markup.Xaml -{ - /// - /// Loads XAML for a avalonia application. - /// - public class AvaloniaXamlLoaderPortableXaml - { - private readonly AvaloniaXamlSchemaContext _context = GetContext(); - - private static AvaloniaXamlSchemaContext GetContext() - { - var result = AvaloniaLocator.Current.GetService(); - - if (result == null) - { - result = AvaloniaXamlSchemaContext.Create(); - - AvaloniaLocator.CurrentMutable - .Bind() - .ToConstant(result); - } - - return result; - } - - /// - /// Initializes a new instance of the class. - /// - public AvaloniaXamlLoaderPortableXaml() - { - } - - /// - /// Loads the XAML into a Avalonia component. - /// - /// The object to load the XAML into. - public static void Load(object obj) - { - Contract.Requires(obj != null); - - var loader = new AvaloniaXamlLoader(); - loader.Load(obj.GetType(), obj); - } - - /// - /// Loads the XAML for a type. - /// - /// The type. - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(Type type, object rootInstance = null) - { - Contract.Requires(type != null); - - // HACK: Currently Visual Studio is forcing us to change the extension of xaml files - // in certain situations, so we try to load .xaml and if that's not found we try .xaml. - // Ideally we'd be able to use .xaml everywhere - var assetLocator = AvaloniaLocator.Current.GetService(); - - if (assetLocator == null) - { - throw new InvalidOperationException( - "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); - } - - foreach (var uri in GetUrisFor(type)) - { - if (assetLocator.Exists(uri)) - { - using (var stream = assetLocator.Open(uri)) - { - var initialize = rootInstance as ISupportInitialize; - initialize?.BeginInit(); - try - { - return Load(stream, rootInstance, uri); - } - finally - { - initialize?.EndInit(); - } - } - } - } - - throw new FileNotFoundException("Unable to find view for " + type.FullName); - } - - /// - /// Loads XAML from a URI. - /// - /// The URI of the XAML file. - /// - /// A base URI to use if is relative. - /// - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) - { - Contract.Requires(uri != null); - - var assetLocator = AvaloniaLocator.Current.GetService(); - - if (assetLocator == null) - { - throw new InvalidOperationException( - "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); - } - - using (var stream = assetLocator.Open(uri, baseUri)) - { - try - { - return Load(stream, rootInstance, uri); - } - catch (Exception e) - { - var uriString = uri.ToString(); - if (!uri.IsAbsoluteUri) - { - uriString = new Uri(baseUri, uri).AbsoluteUri; - } - throw new XamlLoadException("Error loading xaml at " + uriString, e); - } - } - } - - /// - /// Loads XAML from a string. - /// - /// The string containing the XAML. - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(string xaml, object rootInstance = null) - { - Contract.Requires(xaml != null); - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) - { - return Load(stream, rootInstance); - } - } - - /// - /// Loads XAML from a stream. - /// - /// The stream containing the XAML. - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The URI of the XAML - /// The loaded object. - public object Load(Stream stream, object rootInstance = null, Uri uri = null) - { - var readerSettings = new XamlXmlReaderSettings() - { - BaseUri = uri, - LocalAssembly = rootInstance?.GetType().GetTypeInfo().Assembly - }; - - var reader = new XamlXmlReader(stream, _context, readerSettings); - - object result = LoadFromReader( - reader, - AvaloniaXamlContext.For(readerSettings, rootInstance)); - - var topLevel = result as TopLevel; - - if (topLevel != null) - { - DelayedBinding.ApplyBindings(topLevel); - } - - return result; - } - - internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null) - { - var writer = AvaloniaXamlObjectWriter.Create( - reader.SchemaContext, - context); - - XamlServices.Transform(reader, writer); - writer.ApplyAllDelayedProperties(); - return writer.Result; - } - - internal static object LoadFromReader(XamlReader reader) - { - //return XamlServices.Load(reader); - return LoadFromReader(reader, null); - } - - /// - /// Gets the URI for a type. - /// - /// The type. - /// The URI. - private static IEnumerable GetUrisFor(Type type) - { - var asm = type.GetTypeInfo().Assembly.GetName().Name; - var typeName = type.FullName; - yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm); - yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm); - } - } -} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index 9089a13656..8e71c5f81b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -36,13 +36,6 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions // Look upwards though the ambient context for IResourceProviders which might be able // to give us the resource. - // - // TODO: If we're in a template then only the ambient values since the root of the - // template wil be included here. We need some way to get hold of the parent ambient - // context and search that. See the test: - // - // StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File - // foreach (var ambientValue in ambientValues) { // We override XamlType.CanAssignTo in BindingXamlType so the results we get back diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs index e0e2553f46..240ca291a8 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs @@ -11,7 +11,8 @@ namespace Avalonia.Markup.Xaml.PortableXaml { public static AvaloniaXamlObjectWriter Create( XamlSchemaContext schemaContext, - AvaloniaXamlContext context) + AvaloniaXamlContext context, + IAmbientProvider parentAmbientProvider = null) { var nameScope = new AvaloniaNameScope { Instance = context?.RootInstance }; @@ -23,8 +24,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml }; return new AvaloniaXamlObjectWriter(schemaContext, - writerSettings.WithContext(context), - nameScope); + writerSettings.WithContext(context), + nameScope, + parentAmbientProvider); } private readonly DelayedValuesHelper _delayedValuesHelper = new DelayedValuesHelper(); @@ -34,9 +36,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml private AvaloniaXamlObjectWriter( XamlSchemaContext schemaContext, XamlObjectWriterSettings settings, - AvaloniaNameScope nameScope - ) - : base(schemaContext, settings) + AvaloniaNameScope nameScope, + IAmbientProvider parentAmbientProvider) + : base(schemaContext, settings, parentAmbientProvider) { _nameScope = nameScope; } diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index c066401445..faa952f3a0 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit c0664014455392ac221a765e66f9837704339b6f +Subproject commit faa952f3a05b4bdf2986d686f4154b1ab084508a diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs index 1d4dafc413..63fb9f193c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs @@ -10,8 +10,10 @@ namespace Avalonia.Markup.Xaml.Templates public class TemplateContent { - public TemplateContent(IEnumerable namespaces, XamlReader reader) + public TemplateContent(IEnumerable namespaces, XamlReader reader, + IAmbientProvider ambientProvider) { + ParentAmbientProvider = ambientProvider; List = new XamlNodeList(reader.SchemaContext); //we need to rpeserve all namespace and prefixes to writer @@ -26,9 +28,11 @@ namespace Avalonia.Markup.Xaml.Templates public XamlNodeList List { get; } + private IAmbientProvider ParentAmbientProvider { get; } + public IControl Load() { - return (IControl)AvaloniaXamlLoader.LoadFromReader(List.GetReader()); + return (IControl)AvaloniaXamlLoader.LoadFromReader(List.GetReader(), parentAmbientProvider: ParentAmbientProvider); } public static IControl Load(object templateContent) diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs index 1085131230..e29485ddb0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs @@ -14,7 +14,8 @@ namespace Avalonia.Markup.Xaml.Templates { var tdc = (ITypeDescriptorContext)serviceProvider; var ns = tdc.GetService(); - return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader); + var ambientProvider = tdc.GetService(); + return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader, ambientProvider); } public override XamlReader Save(object value, IServiceProvider serviceProvider) diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index e5ba285f4f..8cbc6cbdd8 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -161,6 +161,10 @@ namespace Avalonia.MonoMac Position = pos; } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IScreenImpl Screen { get; diff --git a/src/OSX/Avalonia.MonoMac/WindowImpl.cs b/src/OSX/Avalonia.MonoMac/WindowImpl.cs index 6825fce82e..d01cbd6ae3 100644 --- a/src/OSX/Avalonia.MonoMac/WindowImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowImpl.cs @@ -9,6 +9,7 @@ namespace Avalonia.MonoMac class WindowImpl : WindowBaseImpl, IWindowImpl { public bool IsDecorated = true; + public bool IsResizable = true; public CGRect? UndecoratedLastUnmaximizedFrame; public WindowImpl() @@ -76,10 +77,15 @@ namespace Avalonia.MonoMac protected override NSWindowStyle GetStyle() { + var windowStyle = NSWindowStyle.Borderless; + if (IsDecorated) - return NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable | - NSWindowStyle.Titled; - return NSWindowStyle.Borderless; + windowStyle |= NSWindowStyle.Closable | NSWindowStyle.Miniaturizable | NSWindowStyle.Titled; + + if (IsResizable) + windowStyle |= NSWindowStyle.Resizable; + + return windowStyle; } public void SetSystemDecorations(bool enabled) @@ -88,6 +94,12 @@ namespace Avalonia.MonoMac UpdateStyle(); } + public void CanResize(bool value) + { + IsResizable = value; + UpdateStyle(); + } + public void SetTitle(string title) => Window.Title = title; class ModalDisposable : IDisposable diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 4287fd0b9e..fa11edb57b 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -67,7 +67,23 @@ namespace Avalonia.Shared.PlatformSupport /// /// The resource was not found. /// - public Stream Open(Uri uri, Uri baseUri = null) + public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; + + /// + /// Opens the resource with the requested URI and returns the resource string and the + /// assembly containing the resource. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// + /// The stream containing the resource contents together with the assembly. + /// + /// + /// The resource was not found. + /// + public Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null) { var asset = GetAsset(uri, baseUri); @@ -76,7 +92,7 @@ namespace Avalonia.Shared.PlatformSupport throw new FileNotFoundException($"The resource {uri} could not be found."); } - return asset.GetStream(); + return Tuple.Create(asset.GetStream(), asset.Assembly); } private IAssetDescriptor GetAsset(Uri uri, Uri baseUri) @@ -162,6 +178,7 @@ namespace Avalonia.Shared.PlatformSupport private interface IAssetDescriptor { Stream GetStream(); + Assembly Assembly { get; } } private class AssemblyResourceDescriptor : IAssetDescriptor @@ -179,6 +196,8 @@ namespace Avalonia.Shared.PlatformSupport { return _asm.GetManifestResourceStream(_name); } + + public Assembly Assembly => _asm; } private class AssemblyDescriptor diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index 34867765e5..13d5f662c2 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -7,7 +7,6 @@ using System.Text; using Avalonia.Input; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Input.IDataObject; -using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; using System.IO; using System.Runtime.Serialization.Formatters.Binary; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9b344f1bb4..5c24aa1c69 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -558,7 +558,18 @@ namespace Avalonia.Win32.Interop { DIB_RGB_COLORS = 0, /* color table in RGBs */ DIB_PAL_COLORS /* color table in palette indices */ - }; + } + + public enum WindowLongParam + { + GWL_WNDPROC = -4, + GWL_HINSTANCE = -6, + GWL_HWNDPARENT = -8, + GWL_ID = -12, + GWL_STYLE = -16, + GWL_EXSTYLE = -20, + GWL_USERDATA = -21 + } [StructLayout(LayoutKind.Sequential)] public struct RGBQUAD @@ -615,6 +626,16 @@ namespace Avalonia.Win32.Interop public uint[] cols; } + [StructLayout(LayoutKind.Sequential)] + public struct MINMAXINFO + { + public POINT ptReserved; + public POINT ptMaxSize; + public POINT ptMaxPosition; + public POINT ptMinTrackSize; + public POINT ptMaxTrackSize; + } + public const int SizeOf_BITMAPINFOHEADER = 40; [DllImport("user32.dll")] @@ -977,7 +998,7 @@ namespace Avalonia.Win32.Interop public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch); [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] - public static extern void DoDragDrop(IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect); + public static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect); @@ -1369,13 +1390,13 @@ namespace Avalonia.Win32.Interop internal interface IDropTarget { [PreserveSig] - UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); [PreserveSig] UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); [PreserveSig] UnmanagedMethods.HRESULT DragLeave(); [PreserveSig] - UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); } [ComImport] @@ -1390,6 +1411,27 @@ namespace Avalonia.Win32.Interop } + [ComImport] + [Guid("0000010E-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOleDataObject + { + void GetData([In] ref FORMATETC format, out STGMEDIUM medium); + void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium); + [PreserveSig] + int QueryGetData([In] ref FORMATETC format); + [PreserveSig] + int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut); + void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium, [MarshalAs(UnmanagedType.Bool)] bool release); + IEnumFORMATETC EnumFormatEtc(DATADIR direction); + [PreserveSig] + int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection); + void DUnadvise(int connection); + [PreserveSig] + int EnumDAdvise(out IEnumSTATDATA enumAdvise); + } + + [StructLayoutAttribute(LayoutKind.Sequential)] internal struct _DROPFILES { diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index 85d1daadeb..d7b663e7bf 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -8,15 +8,14 @@ using System.Runtime.Serialization.Formatters.Binary; using System.Text; using Avalonia.Input; using Avalonia.Win32.Interop; -using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace Avalonia.Win32 { class OleDataObject : Avalonia.Input.IDataObject { - private IDataObject _wrapped; + private IOleDataObject _wrapped; - public OleDataObject(IDataObject wrapped) + public OleDataObject(IOleDataObject wrapped) { _wrapped = wrapped; } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 500c03e317..973564a3d1 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -3,7 +3,6 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Input.IDataObject; -using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace Avalonia.Win32 { diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 95077f82a1..a5088e794c 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -82,11 +82,11 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); - if (OleContext.Current != null) - AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - UseDeferredRendering = deferredRendering; _uiThread = UnmanagedMethods.GetCurrentThreadId(); + + if (OleContext.Current != null) + AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); } public bool HasMessages() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index c3fab0e6b9..cf6cb40e58 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -31,10 +31,14 @@ namespace Avalonia.Win32 private IInputRoot _owner; private bool _trackingMouse; private bool _decorated = true; + private bool _resizable = true; private double _scaling = 1; private WindowState _showWindowState; private FramebufferManager _framebuffer; private OleDropTarget _dropTarget; + private Size _minSize; + private Size _maxSize; + #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; #endif @@ -77,8 +81,8 @@ namespace Avalonia.Win32 { get { - var style = UnmanagedMethods.GetWindowLong(_hwnd, -16); - var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, -20); + var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); var padding = new UnmanagedMethods.RECT(); if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) @@ -102,6 +106,12 @@ namespace Avalonia.Win32 } } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + _minSize = minSize; + _maxSize = maxSize; + } + public IScreenImpl Screen { get; @@ -235,13 +245,19 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); - style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + var systemDecorationStyles = UnmanagedMethods.WindowStyles.WS_OVERLAPPED + | UnmanagedMethods.WindowStyles.WS_CAPTION + | UnmanagedMethods.WindowStyles.WS_SYSMENU + | UnmanagedMethods.WindowStyles.WS_MINIMIZEBOX + | UnmanagedMethods.WindowStyles.WS_MAXIMIZEBOX; + + style |= systemDecorationStyles; if (!value) { - style ^= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + style ^= systemDecorationStyles; } UnmanagedMethods.RECT windowRect; @@ -251,7 +267,7 @@ namespace Avalonia.Win32 Rect newRect; var oldThickness = BorderThickness; - UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint)style); + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); if (value) { @@ -614,7 +630,26 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_MOVE: PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16))); return IntPtr.Zero; - + + case UnmanagedMethods.WindowsMessage.WM_GETMINMAXINFO: + + MINMAXINFO mmi = Marshal.PtrToStructure(lParam); + + if (_minSize.Width > 0) + mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + + if (_minSize.Height > 0) + mmi.ptMinTrackSize.Y = (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + + if (!Double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) + mmi.ptMaxTrackSize.X = (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + + if (!Double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0) + mmi.ptMaxTrackSize.Y = (int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + + Marshal.StructureToPtr(mmi, lParam, true); + return IntPtr.Zero; + case UnmanagedMethods.WindowsMessage.WM_DISPLAYCHANGE: (Screen as ScreenImpl)?.InvalidateScreensCache(); return IntPtr.Zero; @@ -801,11 +836,11 @@ namespace Avalonia.Win32 public void ShowTaskbarIcon(bool value) { - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -20); - - style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); + + style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); - style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW; + style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW; if (value) style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW; else @@ -816,9 +851,27 @@ namespace Avalonia.Win32 { //Toggle to make the styles stick UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide); - UnmanagedMethods.SetWindowLong(_hwnd, -20, (uint)style); + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE, (uint)style); UnmanagedMethods.ShowWindow(_hwnd, windowPlacement.ShowCmd); } } + + public void CanResize(bool value) + { + if (value == _resizable) + { + return; + } + + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + + if (value) + style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; + else + style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); + _resizable = value; + } } } diff --git a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs index 50a3cd9ec0..3d8bafeca9 100644 --- a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs +++ b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs @@ -14,6 +14,10 @@ namespace Avalonia.iOS } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IDisposable ShowDialog() { return Disposable.Empty; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs new file mode 100644 index 0000000000..4b0bee00f3 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions +{ + public class BindingExtensionTests + { + + [Fact] + public void BindingExtension_Binds_To_Source() + { + using (StyledWindow()) + { + var xaml = @" + + + foobar + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + window.Show(); + + Assert.Equal("foobar", textBlock.Text); + } + } + + private IDisposable StyledWindow(params (string, string)[] assets) + { + var services = TestServices.StyledWindow.With( + assetLoader: new MockAssetLoader(assets), + theme: () => new Styles + { + WindowStyle(), + }); + + return UnitTestApplication.Start(services); + } + + private Style WindowStyle() + { + return new Style(x => x.OfType()) + { + Setters = + { + new Setter( + Window.TemplateProperty, + new FuncControlTemplate(x => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], + })) + } + }; + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index 862ce2b3c0..8615efd967 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -323,7 +323,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } - [Fact(Skip = "Not yet supported by Portable.Xaml")] + [Fact] public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File() { var styleXaml = @" @@ -361,9 +361,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var border = (Border)button.GetVisualChildren().Single(); var brush = (SolidColorBrush)border.Background; - - // To make this work we somehow need to be able to get hold of the parent ambient - // context from Portable.Xaml. See TODO in StaticResourceExtension. + Assert.Equal(0xff506070, brush.Color.ToUint32()); } } @@ -417,6 +415,79 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void StaticResource_Can_Be_Assigned_To_Binding_Converter_In_DataTemplate() + { + using (StyledWindow()) + { + var xaml = @" + + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + + window.DataContext = "foo"; + var presenter = window.FindControl("presenter"); + + window.Show(); + + var textBlock = (TextBlock)presenter.GetVisualChildren().Single(); + + Assert.NotNull(textBlock); + Assert.Equal("foobar", textBlock.Text); + } + } + + [Fact] + public void StaticResource_Is_Correctly_Chosen_From_Within_DataTemplate() + { + // this tests if IAmbientProviders in DataTemplate contexts are in correct order + // if they wouldn't be, Purple brush would be bound to + using (StyledWindow()) + { + var xaml = @" + + + + + + + + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + + window.Show(); + + var textBlock = window.GetVisualDescendants().OfType().Single(); + + Assert.NotNull(textBlock); + Assert.Equal("White-bar", textBlock.Text); + } + } + [Fact] public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() { diff --git a/tests/Avalonia.UnitTests/MockAssetLoader.cs b/tests/Avalonia.UnitTests/MockAssetLoader.cs index 5cff20c6a1..d6b70aee16 100644 --- a/tests/Avalonia.UnitTests/MockAssetLoader.cs +++ b/tests/Avalonia.UnitTests/MockAssetLoader.cs @@ -27,6 +27,11 @@ namespace Avalonia.UnitTests { return new MemoryStream(Encoding.UTF8.GetBytes(_assets[uri])); } + + public Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null) + { + return Tuple.Create(Open(uri, baseUri), (Assembly)null); + } public void SetDefaultAssembly(Assembly asm) {