From d9607a5fca989531e3ef4689aa13dea9db82cd91 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 23 Aug 2016 15:44:13 +0300 Subject: [PATCH] Initial (with bugs) gtk embedding implementation --- Avalonia.sln | 43 ++ samples/ControlCatalog.Desktop/Program.cs | 4 +- samples/interop/GtkInteropDemo/App.config | 6 + .../GtkInteropDemo/GtkInteropDemo.csproj | 156 ++++++++ samples/interop/GtkInteropDemo/MainWindow.cs | 28 ++ samples/interop/GtkInteropDemo/Program.cs | 24 ++ .../GtkInteropDemo/Properties/AssemblyInfo.cs | 36 ++ .../Properties/Resources.Designer.cs | 71 ++++ .../GtkInteropDemo/Properties/Resources.resx | 117 ++++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + .../EmbeddableControlRoot.cs | 7 +- src/Gtk/Avalonia.Cairo/CairoPlatform.cs | 22 +- src/Gtk/Avalonia.Cairo/RenderTarget.cs | 20 +- src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj | 8 + src/Gtk/Avalonia.Gtk/EmbeddableImpl.cs | 79 ++++ .../Embedding/GtkAvaloniaControlHost.cs | 53 +++ src/Gtk/Avalonia.Gtk/GtkPlatform.cs | 5 +- src/Gtk/Avalonia.Gtk/SystemDialogImpl.cs | 4 +- src/Gtk/Avalonia.Gtk/WindowImpl.cs | 374 +++--------------- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 323 +++++++++++++++ src/Gtk/Avalonia.Gtk/Windows.cs | 91 +++++ 22 files changed, 1157 insertions(+), 351 deletions(-) create mode 100644 samples/interop/GtkInteropDemo/App.config create mode 100644 samples/interop/GtkInteropDemo/GtkInteropDemo.csproj create mode 100644 samples/interop/GtkInteropDemo/MainWindow.cs create mode 100644 samples/interop/GtkInteropDemo/Program.cs create mode 100644 samples/interop/GtkInteropDemo/Properties/AssemblyInfo.cs create mode 100644 samples/interop/GtkInteropDemo/Properties/Resources.Designer.cs create mode 100644 samples/interop/GtkInteropDemo/Properties/Resources.resx create mode 100644 samples/interop/GtkInteropDemo/Properties/Settings.Designer.cs create mode 100644 samples/interop/GtkInteropDemo/Properties/Settings.settings create mode 100644 src/Gtk/Avalonia.Gtk/EmbeddableImpl.cs create mode 100644 src/Gtk/Avalonia.Gtk/Embedding/GtkAvaloniaControlHost.cs create mode 100644 src/Gtk/Avalonia.Gtk/WindowImplBase.cs create mode 100644 src/Gtk/Avalonia.Gtk/Windows.cs diff --git a/Avalonia.sln b/Avalonia.sln index abbeeab2fa..5c0ee85216 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -167,6 +167,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{A0CC EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{C7A69145-60B6-4882-97D6-A3921DD43978}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GtkInteropDemo", "samples\interop\GtkInteropDemo\GtkInteropDemo.csproj", "{BD7F352C-6DC1-4740-BAF2-2D34A038728C}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 @@ -2286,6 +2288,46 @@ Global {C7A69145-60B6-4882-97D6-A3921DD43978}.Release|Mono.Build.0 = Release|Any CPU {C7A69145-60B6-4882-97D6-A3921DD43978}.Release|x86.ActiveCfg = Release|Any CPU {C7A69145-60B6-4882-97D6-A3921DD43978}.Release|x86.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|Any CPU.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|iPhone.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|Mono.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|Mono.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|x86.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.AppStore|x86.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|iPhone.Build.0 = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|Mono.ActiveCfg = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|Mono.Build.0 = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|x86.ActiveCfg = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Debug|x86.Build.0 = Debug|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|Any CPU.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|iPhone.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|iPhone.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|Mono.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|Mono.Build.0 = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|x86.ActiveCfg = Release|Any CPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2340,5 +2382,6 @@ Global {FBCAF3D0-2808-4934-8E96-3F607594517B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {A0CC0258-D18C-4AB3-854F-7101680FC3F9} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C7A69145-60B6-4882-97D6-A3921DD43978} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} + {BD7F352C-6DC1-4740-BAF2-2D34A038728C} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} EndGlobalSection EndGlobal diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b67c5ea51d..d039cce83c 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -16,8 +16,8 @@ namespace ControlCatalog // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. - AppBuilder.Configure() - .UsePlatformDetect() + AppBuilder.Configure().UseGtk().UseCairo() + //.UsePlatformDetect() .Start(); } diff --git a/samples/interop/GtkInteropDemo/App.config b/samples/interop/GtkInteropDemo/App.config new file mode 100644 index 0000000000..8324aa6ff1 --- /dev/null +++ b/samples/interop/GtkInteropDemo/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj b/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj new file mode 100644 index 0000000000..9f69090ce5 --- /dev/null +++ b/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj @@ -0,0 +1,156 @@ + + + + + Debug + AnyCPU + {BD7F352C-6DC1-4740-BAF2-2D34A038728C} + WinExe + Properties + GtkInteropDemo + GtkInteropDemo + v4.6 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Avalonia.Animation + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Avalonia.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Avalonia.Controls + + + {7062ae20-5dcc-4442-9645-8195bdece63e} + Avalonia.Diagnostics + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Avalonia.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Avalonia.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Avalonia.Layout + + + {6417b24e-49c2-4985-8db2-3ab9d898ec91} + Avalonia.ReactiveUI + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Avalonia.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Avalonia.Styling + + + {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} + Avalonia.Themes.Default + + + {fb05ac90-89ba-4f2f-a924-f37875fb547c} + Avalonia.Cairo + + + {54f237d5-a70a-4752-9656-0c70b1a7b047} + Avalonia.Gtk + + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Avalonia.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Avalonia.Markup + + + {925dd807-b651-475f-9f7c-cbeb974ce43d} + Avalonia.Skia.Desktop + + + {d0a739b9-3c68-4ba6-a328-41606954b6bd} + ControlCatalog + + + + + \ No newline at end of file diff --git a/samples/interop/GtkInteropDemo/MainWindow.cs b/samples/interop/GtkInteropDemo/MainWindow.cs new file mode 100644 index 0000000000..2d7521cd8c --- /dev/null +++ b/samples/interop/GtkInteropDemo/MainWindow.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Diagnostics; +using Avalonia.Gtk.Embedding; +using ControlCatalog; +using Gtk; + +namespace GtkInteropDemo +{ + class MainWindow : Window + { + public MainWindow() : base("Gtk Embedding Demo") + { + var root = new HBox(); + var left = new VBox(); + left.Add(new Button("I'm GTK button")); + left.Add(new Calendar()); + root.PackEnd(left, false, false, 0); + root.PackStart(new GtkAvaloniaControlHost() {Content = new ControlCatalogControl()}, true, true, 0); + Add(root); + + ShowAll(); + } + } +} diff --git a/samples/interop/GtkInteropDemo/Program.cs b/samples/interop/GtkInteropDemo/Program.cs new file mode 100644 index 0000000000..7dd39c74ca --- /dev/null +++ b/samples/interop/GtkInteropDemo/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using ControlCatalog; + +namespace GtkInteropDemo +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + AppBuilder.Configure().UseGtk().UseCairo().SetupWithoutStarting(); + new MainWindow().Show(); + Gtk.Application.Run(); + } + } +} diff --git a/samples/interop/GtkInteropDemo/Properties/AssemblyInfo.cs b/samples/interop/GtkInteropDemo/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..8aaf28c2f0 --- /dev/null +++ b/samples/interop/GtkInteropDemo/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GtkInteropDemo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GtkInteropDemo")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bd7f352c-6dc1-4740-baf2-2d34a038728c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/interop/GtkInteropDemo/Properties/Resources.Designer.cs b/samples/interop/GtkInteropDemo/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..f79a8bf680 --- /dev/null +++ b/samples/interop/GtkInteropDemo/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GtkInteropDemo.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GtkInteropDemo.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/samples/interop/GtkInteropDemo/Properties/Resources.resx b/samples/interop/GtkInteropDemo/Properties/Resources.resx new file mode 100644 index 0000000000..af7dbebbac --- /dev/null +++ b/samples/interop/GtkInteropDemo/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/samples/interop/GtkInteropDemo/Properties/Settings.Designer.cs b/samples/interop/GtkInteropDemo/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..8173ee7dfa --- /dev/null +++ b/samples/interop/GtkInteropDemo/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GtkInteropDemo.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/samples/interop/GtkInteropDemo/Properties/Settings.settings b/samples/interop/GtkInteropDemo/Properties/Settings.settings new file mode 100644 index 0000000000..39645652af --- /dev/null +++ b/samples/interop/GtkInteropDemo/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Avalonia.Controls/EmbeddableControlRoot.cs b/src/Avalonia.Controls/EmbeddableControlRoot.cs index 8d3e0fc6e2..02a34433a7 100644 --- a/src/Avalonia.Controls/EmbeddableControlRoot.cs +++ b/src/Avalonia.Controls/EmbeddableControlRoot.cs @@ -13,6 +13,11 @@ namespace Avalonia.Controls { public class EmbeddableControlRoot : TopLevel, IStyleable, IFocusScope, INameScope, IDisposable { + public EmbeddableControlRoot(IEmbeddableWindowImpl impl) : base(impl) + { + PlatformImpl.Show(); + } + public EmbeddableControlRoot() : base(PlatformManager.CreateEmbeddableWindow()) { PlatformImpl.Show(); @@ -24,8 +29,8 @@ namespace Avalonia.Controls { EnsureInitialized(); ApplyTemplate(); - LayoutManager.Instance.ExecuteInitialLayoutPass(this); PlatformImpl.Show(); + LayoutManager.Instance.ExecuteInitialLayoutPass(this); } diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index 5b85e6c68f..2b0fce8210 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -53,13 +53,21 @@ namespace Avalonia.Cairo public IRenderTarget CreateRenderer(IPlatformHandle handle) { var window = handle as Gtk.Window; - if (window == null) - throw new NotSupportedException(string.Format( - "Don't know how to create a Cairo renderer from a '{0}' handle which isn't Gtk.Window", - handle.HandleDescriptor)); - - window.DoubleBuffered = true; - return new RenderTarget(window); + if (window != null) + { + window.DoubleBuffered = true; + return new RenderTarget(window); + } + var area = handle as Gtk.DrawingArea; + if (area != null) + { + area.DoubleBuffered = true; + return new RenderTarget(area); + } + + throw new NotSupportedException(string.Format( + "Don't know how to create a Cairo renderer from a '{0}' handle which isn't Gtk.Window or Gtk.DrawingArea", + handle.HandleDescriptor)); } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/src/Gtk/Avalonia.Cairo/RenderTarget.cs b/src/Gtk/Avalonia.Cairo/RenderTarget.cs index cb7241f32e..cfa51852d9 100644 --- a/src/Gtk/Avalonia.Cairo/RenderTarget.cs +++ b/src/Gtk/Avalonia.Cairo/RenderTarget.cs @@ -7,6 +7,7 @@ using Avalonia.Cairo.Media; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; +using Gtk; using DrawingContext = Avalonia.Media.DrawingContext; namespace Avalonia.Cairo @@ -20,6 +21,7 @@ namespace Avalonia.Cairo { private readonly Surface _surface; private readonly Gtk.Window _window; + private readonly Gtk.DrawingArea _area; /// /// Initializes a new instance of the class. @@ -37,17 +39,25 @@ namespace Avalonia.Cairo _surface = surface; } + public RenderTarget(DrawingArea area) + { + _area = area; + } /// /// Creates a cairo surface that targets a platform-specific resource. /// /// A surface wrapped in an . - public DrawingContext CreateDrawingContext() + public DrawingContext CreateDrawingContext() => new DrawingContext(CreateMediaDrawingContext()); + public IDrawingContextImpl CreateMediaDrawingContext() { - var ctx = _surface != null - ? new Media.DrawingContext(_surface) - : new Media.DrawingContext(_window.GdkWindow); - return new DrawingContext(ctx); + if(_window!=null) + return new Media.DrawingContext(_window.GdkWindow); + if (_surface != null) + return new Media.DrawingContext(_surface); + if(_area!=null) + return new Media.DrawingContext(_area.GdkWindow); + throw new InvalidOperationException(); } public void Dispose() => _surface?.Dispose(); diff --git a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj index fe075bca77..4cf732d56a 100644 --- a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj +++ b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj @@ -46,6 +46,8 @@ + + @@ -54,8 +56,10 @@ + + @@ -67,6 +71,10 @@ {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Avalonia.Base + + {7062ae20-5dcc-4442-9645-8195bdece63e} + Avalonia.Diagnostics + {62024B2D-53EB-4638-B26B-85EEAA54866E} Avalonia.Input diff --git a/src/Gtk/Avalonia.Gtk/EmbeddableImpl.cs b/src/Gtk/Avalonia.Gtk/EmbeddableImpl.cs new file mode 100644 index 0000000000..fdac509e3f --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/EmbeddableImpl.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Platform; +using Gdk; +using Gtk; +using Action = System.Action; +using WindowEdge = Avalonia.Controls.WindowEdge; + +namespace Avalonia.Gtk +{ + class EmbeddableImpl : WindowImplBase, IEmbeddableWindowImpl + { + public event Action LostFocus; + + public EmbeddableImpl(DrawingArea area) : base(area) + { + area.Events = EventMask.AllEventsMask; + area.SizeAllocated += Plug_SizeAllocated; + } + + public EmbeddableImpl() : this(new PlatformHandleAwareDrawingArea()) + { + + } + + private void Plug_SizeAllocated(object o, SizeAllocatedArgs args) + { + Resized?.Invoke(new Size(args.Allocation.Width, args.Allocation.Height)); + } + + public override Size ClientSize + { + get { return new Size(Widget.Allocation.Width, Widget.Allocation.Height); } + set { } + } + + + //Stubs are needed for future GTK designer embedding support + public override void SetTitle(string title) + { + + } + + public override IDisposable ShowDialog() => Disposable.Create(() => { }); + + public override void SetSystemDecorations(bool enabled) + { + + } + + public override void SetIcon(IWindowIconImpl icon) + { + + } + + public override void BeginMoveDrag() + { + + } + + public override void BeginResizeDrag(WindowEdge edge) + { + + } + + public override Point Position + { + get { return new Point(); } + set + { + + } + } + } +} diff --git a/src/Gtk/Avalonia.Gtk/Embedding/GtkAvaloniaControlHost.cs b/src/Gtk/Avalonia.Gtk/Embedding/GtkAvaloniaControlHost.cs new file mode 100644 index 0000000000..07c211d32c --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/Embedding/GtkAvaloniaControlHost.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Diagnostics; +using Avalonia.Layout; +using Avalonia.Platform; +using Gdk; +using Gtk; + +namespace Avalonia.Gtk.Embedding +{ + public class GtkAvaloniaControlHost : DrawingArea, IPlatformHandle + { + private EmbeddableControlRoot _root; + + public GtkAvaloniaControlHost() + { + _root = new EmbeddableControlRoot(new EmbeddableImpl(this)); + _root.Prepare(); + } + + private Control _content; + + public Control Content + { + get { return _content; } + set + { + _content = value; + if (_root != null) + { + _root.Content = value; + _root.Prepare(); + } + } + } + + protected override void OnSizeRequested(ref Requisition requisition) + { + requisition.Width = 700; + requisition.Height = 500; + + } + + + IntPtr IPlatformHandle.Handle => PlatformHandleAwareWindow.GetNativeWindow(GdkWindow); + + string IPlatformHandle.HandleDescriptor => "HWND"; + } +} diff --git a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs index 4db3bd7894..3adf1c9b89 100644 --- a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs +++ b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs @@ -105,10 +105,7 @@ namespace Avalonia.Gtk return new WindowImpl(); } - public IEmbeddableWindowImpl CreateEmbeddableWindow() - { - throw new NotSupportedException(); - } + public IEmbeddableWindowImpl CreateEmbeddableWindow() => new EmbeddableImpl(); public IPopupImpl CreatePopup() { diff --git a/src/Gtk/Avalonia.Gtk/SystemDialogImpl.cs b/src/Gtk/Avalonia.Gtk/SystemDialogImpl.cs index a47a0a0ef8..ed92a25873 100644 --- a/src/Gtk/Avalonia.Gtk/SystemDialogImpl.cs +++ b/src/Gtk/Avalonia.Gtk/SystemDialogImpl.cs @@ -15,7 +15,7 @@ namespace Avalonia.Gtk public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) { var tcs = new TaskCompletionSource(); - var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((WindowImpl)parent), + var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((WindowImplBase)parent).Widget.Toplevel as Window, dialog is OpenFileDialog ? FileChooserAction.Open : FileChooserAction.Save, @@ -57,7 +57,7 @@ namespace Avalonia.Gtk public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) { var tcs = new TaskCompletionSource(); - var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((WindowImpl)parent), + var dlg = new global::Gtk.FileChooserDialog(dialog.Title, ((WindowImplBase)parent).Widget.Toplevel as Window, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Select Folder", ResponseType.Accept) diff --git a/src/Gtk/Avalonia.Gtk/WindowImpl.cs b/src/Gtk/Avalonia.Gtk/WindowImpl.cs index 26ad52135d..c1a69e8932 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImpl.cs @@ -1,404 +1,118 @@ -// 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.Disposables; -using System.Runtime.InteropServices; -using Gdk; -using Avalonia.Controls; -using Avalonia.Input.Raw; using Avalonia.Platform; -using Avalonia.Input; -using Avalonia.Threading; -using Action = System.Action; -using WindowEdge = Avalonia.Controls.WindowEdge; +using Gdk; namespace Avalonia.Gtk { using Gtk = global::Gtk; - - public class WindowImpl : Gtk.Window, IWindowImpl, IPlatformHandle + public class WindowImpl : WindowImplBase { - private IInputRoot _inputRoot; + private Gtk.Window _window; + private Gtk.Window Window => _window ?? (_window = (Gtk.Window) Widget); - private Size _lastClientSize; - private Gtk.IMContext _imContext; - private uint _lastKeyEventTimestamp; - - private static readonly Gdk.Cursor DefaultCursor = new Gdk.Cursor(CursorType.LeftPtr); - - public WindowImpl() - : base(Gtk.WindowType.Toplevel) + public WindowImpl(Gtk.WindowType type) : base(new PlatformHandleAwareWindow(type)) { - DefaultSize = new Gdk.Size(900, 480); Init(); } - public WindowImpl(Gtk.WindowType type) - : base(type) + public WindowImpl() + : base(new PlatformHandleAwareWindow(Gtk.WindowType.Toplevel) {DefaultSize = new Gdk.Size(900, 480)}) { Init(); } - private void Init() + void Init() { - Events = EventMask.PointerMotionMask | - EventMask.ButtonPressMask | - EventMask.ButtonReleaseMask; - _imContext = new Gtk.IMMulticontext(); - _imContext.Commit += ImContext_Commit; - DoubleBuffered = false; - Realize(); + Window.FocusActivated += OnFocusActivated; + Window.ConfigureEvent += OnConfigureEvent; _lastClientSize = ClientSize; } - - protected override void OnRealized () - { - base.OnRealized (); - _imContext.ClientWindow = this.GdkWindow; - } - - public Size ClientSize + private Size _lastClientSize; + void OnConfigureEvent(object o, Gtk.ConfigureEventArgs args) { - get - { - int width; - int height; - GetSize(out width, out height); - return new Size(width, height); - } - - set - { - Resize((int)value.Width, (int)value.Height); - } - } + var evnt = args.Event; + args.RetVal = true; + var newSize = new Size(evnt.Width, evnt.Height); - public Size MaxClientSize - { - get + if (newSize != _lastClientSize) { - // TODO: This should take into account things such as taskbar and window border - // thickness etc. - return new Size(Screen.Width, Screen.Height); + Resized(newSize); + _lastClientSize = newSize; } } - public Avalonia.Controls.WindowState WindowState + public override Size ClientSize { get { - switch (GdkWindow.State) - { - case Gdk.WindowState.Iconified: - return Controls.WindowState.Minimized; - case Gdk.WindowState.Maximized: - return Controls.WindowState.Maximized; - default: - return Controls.WindowState.Normal; - } + int width; + int height; + Window.GetSize(out width, out height); + return new Size(width, height); } set { - switch (value) - { - case Controls.WindowState.Minimized: - GdkWindow.Iconify(); - break; - case Controls.WindowState.Maximized: - GdkWindow.Maximize(); - break; - case Controls.WindowState.Normal: - GdkWindow.Deiconify(); - GdkWindow.Unmaximize(); - break; - } + Window.Resize((int)value.Width, (int)value.Height); } } - public double Scaling => 1; - - IPlatformHandle ITopLevelImpl.Handle => this; - - [DllImport("libgdk-win32-2.0-0.dll", CallingConvention = CallingConvention.Cdecl)] - extern static IntPtr gdk_win32_drawable_get_handle(IntPtr gdkWindow); - - [DllImport("libgtk-x11-2.0.so.0", CallingConvention = CallingConvention.Cdecl)] - extern static IntPtr gdk_x11_drawable_get_xid(IntPtr gdkWindow); - - [DllImport("libgdk-quartz-2.0-0.dylib", CallingConvention = CallingConvention.Cdecl)] - extern static IntPtr gdk_quartz_window_get_nswindow(IntPtr gdkWindow); - - IntPtr _nativeWindow; - - IntPtr GetNativeWindow() + public override void SetTitle(string title) { - IntPtr h = GdkWindow.Handle; - if (_nativeWindow != IntPtr.Zero) - return _nativeWindow; - //Try whatever backend that works - try - { - return _nativeWindow = gdk_quartz_window_get_nswindow(h); - } - catch - { - } - try - { - return _nativeWindow = gdk_x11_drawable_get_xid(h); - } - catch - { - } - return _nativeWindow = gdk_win32_drawable_get_handle(h); + Window.Title = title; } - - IntPtr IPlatformHandle.Handle => GetNativeWindow(); - public string HandleDescriptor => "HWND"; - - public Action Activated { get; set; } - - public Action Closed { get; set; } - - public Action Deactivated { get; set; } - - public Action Input { get; set; } - - public Action Paint { get; set; } - - public Action Resized { get; set; } - - public Action ScalingChanged { get; set; } - - public IPopupImpl CreatePopup() - { - return new PopupImpl(); - } - - public void Invalidate(Rect rect) - { - if (base.GdkWindow != null) - base.GdkWindow.InvalidateRect( - new Rectangle((int) rect.X, (int) rect.Y, (int) rect.Width, (int) rect.Height), true); - } - - public Point PointToClient(Point point) + void OnFocusActivated(object sender, EventArgs eventArgs) { - int x, y; - GdkWindow.GetDeskrelativeOrigin(out x, out y); - - return new Point(point.X - x, point.Y - y); - } - - public Point PointToScreen(Point point) - { - int x, y; - GdkWindow.GetDeskrelativeOrigin(out x, out y); - - return new Point(point.X + x, point.Y + y); - } - - public void SetInputRoot(IInputRoot inputRoot) - { - _inputRoot = inputRoot; - } - - public void SetTitle(string title) - { - Title = title; - } - - - public void SetCursor(IPlatformHandle cursor) - { - GdkWindow.Cursor = cursor != null ? new Gdk.Cursor(cursor.Handle) : DefaultCursor; + Activated(); } - public void BeginMoveDrag() + public override void BeginMoveDrag() { int x, y; ModifierType mod; - Screen.RootWindow.GetPointer(out x, out y, out mod); - BeginMoveDrag(1, x, y, 0); + Window.Screen.RootWindow.GetPointer(out x, out y, out mod); + Window.BeginMoveDrag(1, x, y, 0); } - public void BeginResizeDrag(WindowEdge edge) + public override void BeginResizeDrag(Controls.WindowEdge edge) { int x, y; ModifierType mod; - Screen.RootWindow.GetPointer(out x, out y, out mod); - BeginResizeDrag((Gdk.WindowEdge) (int) edge, 1, x, y, 0); + Window.Screen.RootWindow.GetPointer(out x, out y, out mod); + Window.BeginResizeDrag((Gdk.WindowEdge)(int)edge, 1, x, y, 0); } - public Point Position + public override Point Position { get { int x, y; - GetPosition(out x, out y); + Window.GetPosition(out x, out y); return new Point(x, y); } set { - Move((int)value.X, (int)value.Y); + Window.Move((int)value.X, (int)value.Y); } } - public IDisposable ShowDialog() + public override IDisposable ShowDialog() { - Modal = true; - Show(); + Window.Modal = true; + Window.Show(); return Disposable.Empty; } - public void SetSystemDecorations(bool enabled) => Decorated = enabled; - - void ITopLevelImpl.Activate() - { - Activate(); - } - - private static InputModifiers GetModifierKeys(ModifierType state) - { - var rv = InputModifiers.None; - if (state.HasFlag(ModifierType.ControlMask)) - rv |= InputModifiers.Control; - if (state.HasFlag(ModifierType.ShiftMask)) - rv |= InputModifiers.Shift; - if (state.HasFlag(ModifierType.Mod1Mask)) - rv |= InputModifiers.Control; - if(state.HasFlag(ModifierType.Button1Mask)) - rv |= InputModifiers.LeftMouseButton; - if (state.HasFlag(ModifierType.Button2Mask)) - rv |= InputModifiers.RightMouseButton; - if (state.HasFlag(ModifierType.Button3Mask)) - rv |= InputModifiers.MiddleMouseButton; - return rv; - } - - protected override bool OnButtonPressEvent(EventButton evnt) - { - - var e = new RawMouseEventArgs( - GtkMouseDevice.Instance, - evnt.Time, - _inputRoot, - evnt.Button == 1 - ? RawMouseEventType.LeftButtonDown - : evnt.Button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown, - new Point(evnt.X, evnt.Y), GetModifierKeys(evnt.State)); - Input(e); - return true; - } - - protected override bool OnScrollEvent(EventScroll evnt) - { - double step = 1; - var delta = new Vector(); - if (evnt.Direction == ScrollDirection.Down) - delta = new Vector(0, -step); - else if (evnt.Direction == ScrollDirection.Up) - delta = new Vector(0, step); - else if (evnt.Direction == ScrollDirection.Right) - delta = new Vector(-step, 0); - if (evnt.Direction == ScrollDirection.Left) - delta = new Vector(step, 0); - var e = new RawMouseWheelEventArgs(GtkMouseDevice.Instance, evnt.Time, _inputRoot, new Point(evnt.X, evnt.Y), delta, GetModifierKeys(evnt.State)); - Input(e); - return base.OnScrollEvent(evnt); - } - - protected override bool OnButtonReleaseEvent(EventButton evnt) - { - var e = new RawMouseEventArgs( - GtkMouseDevice.Instance, - evnt.Time, - _inputRoot, - evnt.Button == 1 - ? RawMouseEventType.LeftButtonUp - : evnt.Button == 3 ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp, - new Point(evnt.X, evnt.Y), GetModifierKeys(evnt.State)); - Input(e); - return true; - } - - protected override bool OnConfigureEvent(EventConfigure evnt) - { - var newSize = new Size(evnt.Width, evnt.Height); - - if (newSize != _lastClientSize) - { - Resized(newSize); - _lastClientSize = newSize; - } - - return true; - } - - protected override void OnDestroyed() - { - Closed(); - } - - private bool ProcessKeyEvent(EventKey evnt) - { - _lastKeyEventTimestamp = evnt.Time; - if (_imContext.FilterKeypress(evnt)) - return true; - var e = new RawKeyEventArgs( - GtkKeyboardDevice.Instance, - evnt.Time, - evnt.Type == EventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, - GtkKeyboardDevice.ConvertKey(evnt.Key), GetModifierKeys(evnt.State)); - Input(e); - return true; - } - - protected override bool OnKeyPressEvent(EventKey evnt) => ProcessKeyEvent(evnt); - - protected override bool OnKeyReleaseEvent(EventKey evnt) => ProcessKeyEvent(evnt); - - private void ImContext_Commit(object o, Gtk.CommitArgs args) - { - Input(new RawTextInputEventArgs(GtkKeyboardDevice.Instance, _lastKeyEventTimestamp, args.Str)); - } - - protected override bool OnExposeEvent(EventExpose evnt) - { - Paint(evnt.Area.ToAvalonia()); - return true; - } - - protected override void OnFocusActivated() - { - Activated(); - } - - protected override bool OnMotionNotifyEvent(EventMotion evnt) - { - var position = new Point(evnt.X, evnt.Y); - - GtkMouseDevice.Instance.SetClientPosition(position); - - var e = new RawMouseEventArgs( - GtkMouseDevice.Instance, - evnt.Time, - _inputRoot, - RawMouseEventType.Move, - position, GetModifierKeys(evnt.State)); - Input(e); - return true; - } + public override void SetSystemDecorations(bool enabled) => Window.Decorated = enabled; - public void SetIcon(IWindowIconImpl icon) + public override void SetIcon(IWindowIconImpl icon) { - Icon = ((IconImpl)icon).Pixbuf; + Window.Icon = ((IconImpl)icon).Pixbuf; } } -} +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs new file mode 100644 index 0000000000..b81387a678 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -0,0 +1,323 @@ +// 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.Disposables; +using System.Runtime.InteropServices; +using Gdk; +using Avalonia.Controls; +using Avalonia.Input.Raw; +using Avalonia.Platform; +using Avalonia.Input; +using Avalonia.Threading; +using Action = System.Action; +using WindowEdge = Avalonia.Controls.WindowEdge; + +namespace Avalonia.Gtk +{ + using Gtk = global::Gtk; + + public abstract class WindowImplBase : IWindowImpl + { + private IInputRoot _inputRoot; + protected Gtk.Widget _window; + public Gtk.Widget Widget => _window; + + + private Gtk.IMContext _imContext; + + private uint _lastKeyEventTimestamp; + + private static readonly Gdk.Cursor DefaultCursor = new Gdk.Cursor(CursorType.LeftPtr); + + + + protected WindowImplBase(Gtk.Widget window) + { + _window = window; + Init(); + } + + void Init() + { + Handle = _window as IPlatformHandle; + _window.Events = EventMask.AllEventsMask; + _imContext = new Gtk.IMMulticontext(); + _imContext.Commit += ImContext_Commit; + _window.Realized += OnRealized; + _window.DoubleBuffered = false; + _window.Realize(); + _window.ButtonPressEvent += OnButtonPressEvent; + _window.ButtonReleaseEvent += OnButtonReleaseEvent; + _window.ScrollEvent += OnScrollEvent; + _window.Destroyed += OnDestroyed; + _window.KeyPressEvent += OnKeyPressEvent; + _window.KeyReleaseEvent += OnKeyReleaseEvent; + _window.ExposeEvent += OnExposeEvent; + _window.MotionNotifyEvent += OnMotionNotifyEvent; + + } + + public IPlatformHandle Handle { get; private set; } + + void OnRealized (object sender, EventArgs eventArgs) + { + _imContext.ClientWindow = _window.GdkWindow; + } + + public abstract Size ClientSize { get; set; } + + + public Size MaxClientSize + { + get + { + // TODO: This should take into account things such as taskbar and window border + // thickness etc. + return new Size(_window.Screen.Width, _window.Screen.Height); + } + } + + public Avalonia.Controls.WindowState WindowState + { + get + { + switch (_window.GdkWindow.State) + { + case Gdk.WindowState.Iconified: + return Controls.WindowState.Minimized; + case Gdk.WindowState.Maximized: + return Controls.WindowState.Maximized; + default: + return Controls.WindowState.Normal; + } + } + + set + { + switch (value) + { + case Controls.WindowState.Minimized: + _window.GdkWindow.Iconify(); + break; + case Controls.WindowState.Maximized: + _window.GdkWindow.Maximize(); + break; + case Controls.WindowState.Normal: + _window.GdkWindow.Deiconify(); + _window.GdkWindow.Unmaximize(); + break; + } + } + } + + public double Scaling => 1; + + + + + + public Action Activated { get; set; } + + public Action Closed { get; set; } + + public Action Deactivated { get; set; } + + public Action Input { get; set; } + + public Action Paint { get; set; } + + public Action Resized { get; set; } + + public Action ScalingChanged { get; set; } + + public IPopupImpl CreatePopup() + { + return new PopupImpl(); + } + + public void Invalidate(Rect rect) + { + if (_window.GdkWindow != null) + _window.GdkWindow.InvalidateRect( + new Rectangle((int) rect.X, (int) rect.Y, (int) rect.Width, (int) rect.Height), true); + } + + public Point PointToClient(Point point) + { + int x, y; + _window.GdkWindow.GetDeskrelativeOrigin(out x, out y); + + return new Point(point.X - x, point.Y - y); + } + + public Point PointToScreen(Point point) + { + int x, y; + _window.GdkWindow.GetDeskrelativeOrigin(out x, out y); + + return new Point(point.X + x, point.Y + y); + } + + public void SetInputRoot(IInputRoot inputRoot) + { + _inputRoot = inputRoot; + } + + public abstract void SetTitle(string title); + public abstract IDisposable ShowDialog(); + public abstract void SetSystemDecorations(bool enabled); + public abstract void SetIcon(IWindowIconImpl icon); + + + public void SetCursor(IPlatformHandle cursor) + { + _window.GdkWindow.Cursor = cursor != null ? new Gdk.Cursor(cursor.Handle) : DefaultCursor; + } + + public void Show() => _window.Show(); + + public void Hide() => _window.Hide(); + public abstract void BeginMoveDrag(); + public abstract void BeginResizeDrag(WindowEdge edge); + public abstract Point Position { get; set; } + + + void ITopLevelImpl.Activate() + { + _window.Activate(); + } + + private static InputModifiers GetModifierKeys(ModifierType state) + { + var rv = InputModifiers.None; + if (state.HasFlag(ModifierType.ControlMask)) + rv |= InputModifiers.Control; + if (state.HasFlag(ModifierType.ShiftMask)) + rv |= InputModifiers.Shift; + if (state.HasFlag(ModifierType.Mod1Mask)) + rv |= InputModifiers.Control; + if(state.HasFlag(ModifierType.Button1Mask)) + rv |= InputModifiers.LeftMouseButton; + if (state.HasFlag(ModifierType.Button2Mask)) + rv |= InputModifiers.RightMouseButton; + if (state.HasFlag(ModifierType.Button3Mask)) + rv |= InputModifiers.MiddleMouseButton; + return rv; + } + + void OnButtonPressEvent(object o, Gtk.ButtonPressEventArgs args) + { + var evnt = args.Event; + var e = new RawMouseEventArgs( + GtkMouseDevice.Instance, + evnt.Time, + _inputRoot, + evnt.Button == 1 + ? RawMouseEventType.LeftButtonDown + : evnt.Button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown, + new Point(evnt.X, evnt.Y), GetModifierKeys(evnt.State)); + Input(e); + } + + void OnScrollEvent(object o, Gtk.ScrollEventArgs args) + { + var evnt = args.Event; + double step = 1; + var delta = new Vector(); + if (evnt.Direction == ScrollDirection.Down) + delta = new Vector(0, -step); + else if (evnt.Direction == ScrollDirection.Up) + delta = new Vector(0, step); + else if (evnt.Direction == ScrollDirection.Right) + delta = new Vector(-step, 0); + if (evnt.Direction == ScrollDirection.Left) + delta = new Vector(step, 0); + var e = new RawMouseWheelEventArgs(GtkMouseDevice.Instance, evnt.Time, _inputRoot, new Point(evnt.X, evnt.Y), delta, GetModifierKeys(evnt.State)); + Input(e); + } + + protected void OnButtonReleaseEvent(object o, Gtk.ButtonReleaseEventArgs args) + { + var evnt = args.Event; + var e = new RawMouseEventArgs( + GtkMouseDevice.Instance, + evnt.Time, + _inputRoot, + evnt.Button == 1 + ? RawMouseEventType.LeftButtonUp + : evnt.Button == 3 ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp, + new Point(evnt.X, evnt.Y), GetModifierKeys(evnt.State)); + Input(e); + } + + + + void OnDestroyed(object sender, EventArgs eventArgs) + { + Closed(); + } + + private void ProcessKeyEvent(EventKey evnt) + { + + _lastKeyEventTimestamp = evnt.Time; + if (_imContext.FilterKeypress(evnt)) + return; + var e = new RawKeyEventArgs( + GtkKeyboardDevice.Instance, + evnt.Time, + evnt.Type == EventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, + GtkKeyboardDevice.ConvertKey(evnt.Key), GetModifierKeys(evnt.State)); + Input(e); + } + + void OnKeyPressEvent(object o, Gtk.KeyPressEventArgs args) + { + args.RetVal = true; + ProcessKeyEvent(args.Event); + } + + + void OnKeyReleaseEvent(object o, Gtk.KeyReleaseEventArgs args) + { + args.RetVal = true; + ProcessKeyEvent(args.Event); + } + + private void ImContext_Commit(object o, Gtk.CommitArgs args) + { + Input(new RawTextInputEventArgs(GtkKeyboardDevice.Instance, _lastKeyEventTimestamp, args.Str)); + } + + void OnExposeEvent(object o, Gtk.ExposeEventArgs args) + { + Paint(args.Event.Area.ToAvalonia()); + args.RetVal = true; + } + + void OnMotionNotifyEvent(object o, Gtk.MotionNotifyEventArgs args) + { + var evnt = args.Event; + var position = new Point(evnt.X, evnt.Y); + + GtkMouseDevice.Instance.SetClientPosition(position); + + var e = new RawMouseEventArgs( + GtkMouseDevice.Instance, + evnt.Time, + _inputRoot, + RawMouseEventType.Move, + position, GetModifierKeys(evnt.State)); + Input(e); + args.RetVal = true; + } + + + + public void Dispose() + { + _window.Dispose(); + } + } +} diff --git a/src/Gtk/Avalonia.Gtk/Windows.cs b/src/Gtk/Avalonia.Gtk/Windows.cs new file mode 100644 index 0000000000..fe325f4114 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/Windows.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Platform; +using Gdk; +using Gtk; +using Window = Gtk.Window; +using WindowType = Gtk.WindowType; + +namespace Avalonia.Gtk +{ + class PlatformHandleAwareWindow : Window, IPlatformHandle + { + public PlatformHandleAwareWindow(WindowType type) : base(type) + { + Events = EventMask.AllEventsMask; + } + + IntPtr IPlatformHandle.Handle => GetNativeWindow(); + public string HandleDescriptor => "HWND"; + + + [DllImport("libgdk-win32-2.0-0.dll", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr gdk_win32_drawable_get_handle(IntPtr gdkWindow); + + [DllImport("libgtk-x11-2.0.so.0", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr gdk_x11_drawable_get_xid(IntPtr gdkWindow); + + [DllImport("libgdk-quartz-2.0-0.dylib", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr gdk_quartz_window_get_nswindow(IntPtr gdkWindow); + + IntPtr _nativeWindow; + + IntPtr GetNativeWindow() + { + if (_nativeWindow != IntPtr.Zero) + return _nativeWindow; + return _nativeWindow = GetNativeWindow(GdkWindow); + } + + public static IntPtr GetNativeWindow(Gdk.Window window) + { + IntPtr h = window.Handle; + + //Try whatever backend that works + try + { + return gdk_quartz_window_get_nswindow(h); + } + catch + { + } + try + { + return gdk_x11_drawable_get_xid(h); + } + catch + { + } + return gdk_win32_drawable_get_handle(h); + } + + protected override bool OnConfigureEvent(EventConfigure evnt) + { + base.OnConfigureEvent(evnt); + return false; + } + } + + class PlatformHandleAwareDrawingArea : DrawingArea, IPlatformHandle + { + + + + IntPtr IPlatformHandle.Handle => GetNativeWindow(); + public string HandleDescriptor => "HWND"; + IntPtr _nativeWindow; + + IntPtr GetNativeWindow() + { + + if (_nativeWindow != IntPtr.Zero) + return _nativeWindow; + Realize(); + return _nativeWindow = PlatformHandleAwareWindow.GetNativeWindow(GdkWindow); + } + } +}