From bb1d2605715749a0400f33b14de98e5d812284af Mon Sep 17 00:00:00 2001 From: Vsevolod Kukol Date: Mon, 14 Mar 2016 18:20:32 +0100 Subject: [PATCH 1/5] [Gtk] Add Popover transparency without alpha support Simulates alpha color support by rendering the Gdk window content behind a popover on its background, if the Gdk screen does not support alpha colors or window opacity. --- Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs | 85 ++++++++++++++++++++---- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs index 6cab3d07c..8b552dacc 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs @@ -127,16 +127,52 @@ protected override void OnSizeAllocated (Gdk.Rectangle allocation) protected override bool OnDrawn (Context cr) { + bool withRoot = true; int w, h; this.GdkWindow.GetSize (out w, out h); // We clear the surface with a transparent color if possible - if (supportAlpha) + if (supportAlpha) { cr.SetSourceRGBA (1.0, 1.0, 1.0, 0.0); - else - cr.SetSourceRGB (1.0, 1.0, 1.0); - cr.Operator = Operator.Source; - cr.Paint (); + cr.Operator = Operator.Source; + cr.Paint (); + } else { // render background with our parent window + cr.Save (); + var scale = GtkWorkarounds.GetScaleFactor (Content); + int x, y, tx, ty; + tx = ty = 0; + GdkWindow.GetPosition (out x, out y); + + var rootAllocation = new Rectangle (x, y, Allocation.Width, Allocation.Height); + var transientAllocation = Rectangle.Zero; + + // using the target window allows us to simulate transparency and + // draw with alpha. But this is only possible if the TransientFor window + // is set and if we don't exceed its bounds. + if (TransientFor != null && TransientFor.GdkWindow != null) { + TransientFor.GdkWindow.GetPosition (out tx, out ty); + transientAllocation = new Rectangle (tx, ty, TransientFor.Allocation.Width, TransientFor.Allocation.Height); + withRoot = !transientAllocation.Contains (rootAllocation); + } + + // if we have no parent window or need to draw outside of its bounds, the + // root window needs to be rendered first to fill clean/invalid areas. + if (withRoot) { + var pbf = RootWindow.ToPixbuf (x, y, Allocation.Width, Allocation.Height); + Gdk.CairoHelper.SetSourcePixbuf (cr, pbf, 0, 0); + cr.Operator = Operator.Source; + cr.Paint (); + } + if (!transientAllocation.IsEmpty) { // is not empty only if we have a valid target + var pbf = TransientFor.GdkWindow.ToPixbuf (0, 0, (int)(transientAllocation.Width * scale), (int)(transientAllocation.Height)); + cr.Translate ((tx - x), (ty - y)); + cr.Scale (w / (w * scale), h / (h * scale)); + Gdk.CairoHelper.SetSourcePixbuf (cr, pbf, 0, 0); + cr.Operator = Operator.Over; + cr.Paint (); + } + cr.Restore (); + } cr.LineWidth = GtkWorkarounds.GetScaleFactor (Content) > 1 ? 2 : 1; var bounds = new Xwt.Rectangle (cr.LineWidth / 2, cr.LineWidth / 2, w - cr.LineWidth, h - cr.LineWidth); @@ -152,14 +188,21 @@ protected override bool OnDrawn (Context cr) // We draw the rectangle path DrawTriangle (cr); - // We use it - if (supportAlpha) + // disable alpha if we are out of parents bounds without real alpha support + // otherwise we would get artifacts when the popup sesizes (root window will + // contain the popoup - from the previous drawing - shining through our + // new background with alpha) + if ((!supportAlpha && withRoot)) { + cr.SetSourceRGB (170d / 255d, 170d / 255d, 170d / 255d); + cr.StrokePreserve (); + cr.SetSourceRGB (BackgroundColor.R, BackgroundColor.G, BackgroundColor.B); + cr.Fill (); + } else { cr.SetSourceRGBA (0.0, 0.0, 0.0, 0.2); - else - cr.SetSourceRGB (238d / 255d, 238d / 255d, 238d / 255d); - cr.StrokePreserve (); - cr.SetSourceRGBA (BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, BackgroundColor.A); - cr.Fill (); + cr.StrokePreserve (); + cr.SetSourceRGBA (BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, BackgroundColor.A); + cr.Fill (); + } return base.OnDrawn (cr); } @@ -242,10 +285,21 @@ public void Show (Xwt.Popover.Position orientation, Xwt.Widget reference, Xwt.Re var parent = (WindowFrameBackend)Toolkit.GetBackend (reference.ParentWindow); if (popover.TransientFor != parent.Window) { - if (popover.TransientFor != null) + if (popover.TransientFor != null) { popover.TransientFor.FocusInEvent -= HandleParentFocusInEvent; + #if XWT_GTK3 + popover.TransientFor.Drawn -= HandleParentExposeEvent; + #else + popover.TransientFor.ExposeEvent -= HandleParentExposeEvent; + #endif + } popover.TransientFor = parent.Window; popover.TransientFor.FocusInEvent += HandleParentFocusInEvent; + #if XWT_GTK3 + popover.TransientFor.Drawn += HandleParentExposeEvent; + #else + popover.TransientFor.ExposeEvent += HandleParentExposeEvent; + #endif } popover.Hidden += (o, args) => sink.OnClosed (); @@ -304,6 +358,11 @@ void HandleParentFocusInEvent (object o, FocusInEventArgs args) Hide (); } + void HandleParentExposeEvent (object o, EventArgs args) + { + popover.QueueDraw (); + } + public void Hide () { popover.Hide (); From 96868d271f50d691e2d47abcc7b55a59d5f140ec Mon Sep 17 00:00:00 2001 From: Vsevolod Kukol Date: Mon, 14 Mar 2016 20:06:19 +0100 Subject: [PATCH 2/5] [Gtk] Remove Popover HiDPI scale experiments --- Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs index 8b552dacc..417d8cd6d 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs @@ -138,7 +138,6 @@ protected override bool OnDrawn (Context cr) cr.Paint (); } else { // render background with our parent window cr.Save (); - var scale = GtkWorkarounds.GetScaleFactor (Content); int x, y, tx, ty; tx = ty = 0; GdkWindow.GetPosition (out x, out y); @@ -164,9 +163,8 @@ protected override bool OnDrawn (Context cr) cr.Paint (); } if (!transientAllocation.IsEmpty) { // is not empty only if we have a valid target - var pbf = TransientFor.GdkWindow.ToPixbuf (0, 0, (int)(transientAllocation.Width * scale), (int)(transientAllocation.Height)); - cr.Translate ((tx - x), (ty - y)); - cr.Scale (w / (w * scale), h / (h * scale)); + var pbf = TransientFor.GdkWindow.ToPixbuf (0, 0, (int)(transientAllocation.Width), (int)(transientAllocation.Height)); + cr.Translate (tx - x, ty - y); Gdk.CairoHelper.SetSourcePixbuf (cr, pbf, 0, 0); cr.Operator = Operator.Over; cr.Paint (); From c99df78913b9f87b355d4b432fdf537d5ce027b8 Mon Sep 17 00:00:00 2001 From: Vsevolod Kukol Date: Thu, 17 Mar 2016 12:24:57 +0100 Subject: [PATCH 3/5] [Gtk] Optimize fake popover transparency impl. * Use gdk_cairo_set_source_window() if possible (Gtk+ 2.24+) * Optimize window to pixbuf rendering with Gtk+ < 2.24 --- Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs | 91 ++++++++++++++++++++++++ Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs | 32 +++++---- 2 files changed, 110 insertions(+), 13 deletions(-) diff --git a/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs b/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs index 98ba5be3a..404b2b471 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs @@ -1104,6 +1104,9 @@ public static void ForceImageOnMenuItem (Gtk.ImageMenuItem mi) [DllImport (GtkInterop.LIBGTK, CallingConvention = CallingConvention.Cdecl)] static extern double gtk_widget_get_scale_factor (IntPtr widget); + [DllImport (GtkInterop.LIBGDK, CallingConvention = CallingConvention.Cdecl)] + static extern double gdk_window_get_scale_factor (IntPtr window); + [DllImport (GtkInterop.LIBGDK, CallingConvention = CallingConvention.Cdecl)] static extern double gdk_screen_get_monitor_scale_factor (IntPtr widget, int monitor); @@ -1179,6 +1182,20 @@ public static double GetScaleFactor (Gtk.Widget w) return 1; } + public static double GetScaleFactor (this Gdk.Window w) + { + if (!supportsHiResIcons) + return 1; + + try { + return gdk_window_get_scale_factor (w.Handle); + } catch (DllNotFoundException) { + } catch (EntryPointNotFoundException) { + } + supportsHiResIcons = false; + return 1; + } + public static double GetScaleFactor (this Gdk.Screen screen, int monitor) { if (!supportsHiResIcons) @@ -1314,6 +1331,80 @@ public static void SetTransparentBgHint (this Gtk.Widget widget, bool enable) { SetData (widget, "transparent-bg-hint", enable); } + + [DllImport (GtkInterop.LIBGDK, CallingConvention = CallingConvention.Cdecl)] + static extern void gdk_cairo_set_source_window (IntPtr cr, IntPtr window, int x, int y); + + public static bool SetSourceWindow (this Cairo.Context cr, Gdk.Window window, int x, int y) + { + if (!(GtkMajorVersion <= 2 && GtkMinorVersion < 24)) { + try { + gdk_cairo_set_source_window (cr.Handle, window.Handle, x, y); + return true; + } catch (DllNotFoundException) { + } catch (EntryPointNotFoundException) { + } + } + return false; + } + + public static bool DrawWindow (this Cairo.Context cr, Gdk.Window window, double src_x, double src_y, double width, double height, Cairo.Operator op) + { + // HACK: RootWindow has always a scale factor of 0 on Mac + double scale = 1; + if (Platform.IsMac && window == window.Screen.RootWindow) + scale = window.Screen.GetScaleFactor (window.Screen.GetMonitorAtWindow (window)); + else scale = window.GetScaleFactor (); + + if (!(GtkMajorVersion <= 2 && GtkMinorVersion < 24)) { + try { + cr.Save (); + cr.Translate (-src_x, -src_y); + gdk_cairo_set_source_window (cr.Handle, window.Handle, 0, 0); + cr.Operator = op; + cr.Paint (); + cr.Restore (); + return true; + } catch (DllNotFoundException) { + } catch (EntryPointNotFoundException) { + } + } + + // FIXME: Pixbuf.FromDrawable does not support HiDPI + if (scale > 1) + return false; + + Gdk.Pixbuf pbf = null; + int w = (int)width, h = (int)height; + // Pixbuf.FromDrawable does not support negaive coordinates, + // render the whole window in this case + if (src_x < 0 || src_y < 0) { + window.GetSize (out w, out h); + pbf = window.ToPixbuf (0, 0, w, h); + } else { + // Render only the requested area, or the whole window + // if it is the RootWindow (retrieving its size takes longer than + // actually rendering it) + if (window != window.Screen.RootWindow) { + window.GetSize (out w, out h); + w = w - (int)src_x; + h = h - (int)src_y; + } + pbf = window.ToPixbuf ((int)src_x, (int)src_y, w, h); + } + if (pbf != null) { + cr.Save (); + cr.Scale (1 / scale, 1 / scale); + if (src_x < 0 || src_y < 0) + cr.Translate (-src_x, -src_y); + Gdk.CairoHelper.SetSourcePixbuf (cr, pbf, 0, 0); + cr.Operator = op; + cr.Paint (); + cr.Restore (); + return true; + } + return false; + } } public struct KeyboardShortcut : IEquatable diff --git a/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs index 417d8cd6d..f358645ae 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs @@ -130,14 +130,16 @@ protected override bool OnDrawn (Context cr) bool withRoot = true; int w, h; this.GdkWindow.GetSize (out w, out h); - + var scale = GtkWorkarounds.GetScaleFactor (this); + + cr.Save (); + // We clear the surface with a transparent color if possible if (supportAlpha) { cr.SetSourceRGBA (1.0, 1.0, 1.0, 0.0); cr.Operator = Operator.Source; cr.Paint (); } else { // render background with our parent window - cr.Save (); int x, y, tx, ty; tx = ty = 0; GdkWindow.GetPosition (out x, out y); @@ -157,22 +159,26 @@ protected override bool OnDrawn (Context cr) // if we have no parent window or need to draw outside of its bounds, the // root window needs to be rendered first to fill clean/invalid areas. if (withRoot) { - var pbf = RootWindow.ToPixbuf (x, y, Allocation.Width, Allocation.Height); - Gdk.CairoHelper.SetSourcePixbuf (cr, pbf, 0, 0); - cr.Operator = Operator.Source; - cr.Paint (); + if (!cr.DrawWindow (RootWindow, x, y, w, h, Operator.Source)) { + cr.SetSourceRGB (1.0, 1.0, 1.0); + cr.Operator = Operator.Source; + cr.Paint (); + } } if (!transientAllocation.IsEmpty) { // is not empty only if we have a valid target - var pbf = TransientFor.GdkWindow.ToPixbuf (0, 0, (int)(transientAllocation.Width), (int)(transientAllocation.Height)); - cr.Translate (tx - x, ty - y); - Gdk.CairoHelper.SetSourcePixbuf (cr, pbf, 0, 0); - cr.Operator = Operator.Over; - cr.Paint (); + // FIXME: Calculate the HiDPI decoration border offset (now hardcoded 0.5) + if (scale > 1 && TransientFor.Decorated) + cr.Translate (0.5, 0); + if (!cr.DrawWindow (TransientFor.GdkWindow, x - tx, y - ty, w, h, Operator.Over)) { + cr.SetSourceRGB (1.0, 1.0, 1.0); + cr.Operator = Operator.Source; + cr.Paint (); + } } - cr.Restore (); } + cr.Restore (); - cr.LineWidth = GtkWorkarounds.GetScaleFactor (Content) > 1 ? 2 : 1; + cr.LineWidth = scale > 1 ? 2 : 1; var bounds = new Xwt.Rectangle (cr.LineWidth / 2, cr.LineWidth / 2, w - cr.LineWidth, h - cr.LineWidth); var calibratedRect = RecalibrateChildRectangle (bounds); // Fill it with one round rectangle From ea6fd67a32f723a9f0eb21541c9432af7e27a206 Mon Sep 17 00:00:00 2001 From: Vsevolod Kukol Date: Mon, 21 Mar 2016 19:52:32 +0100 Subject: [PATCH 4/5] [Gtk] Use gdk_cairo_set_source_window on Linux only On Windows and Mac gdk_cairo_set_source_window() does not work properly. --- Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs b/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs index 404b2b471..76a0ba811 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs @@ -1356,7 +1356,7 @@ public static bool DrawWindow (this Cairo.Context cr, Gdk.Window window, double scale = window.Screen.GetScaleFactor (window.Screen.GetMonitorAtWindow (window)); else scale = window.GetScaleFactor (); - if (!(GtkMajorVersion <= 2 && GtkMinorVersion < 24)) { + if (!Platform.IsMac && !Platform.IsWindows && !(GtkMajorVersion <= 2 && GtkMinorVersion < 24)) { try { cr.Save (); cr.Translate (-src_x, -src_y); From fbbe782aebf1eb98a42a2f9d7a9b51d2eb6c753b Mon Sep 17 00:00:00 2001 From: Vsevolod Kukol Date: Mon, 21 Mar 2016 19:55:12 +0100 Subject: [PATCH 5/5] [Gdk] Fill Popover without any alpha support If the platform has no alpha support and alpha can not be faked, just fill the whole popover with the desired background color and stroke around the whole window. --- Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs | 44 ++++++++++++++++-------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs b/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs index f358645ae..1e683208f 100644 --- a/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs +++ b/Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs @@ -128,6 +128,7 @@ protected override void OnSizeAllocated (Gdk.Rectangle allocation) protected override bool OnDrawn (Context cr) { bool withRoot = true; + bool fakeAlphaFailed = false; int w, h; this.GdkWindow.GetSize (out w, out h); var scale = GtkWorkarounds.GetScaleFactor (this); @@ -163,6 +164,7 @@ protected override bool OnDrawn (Context cr) cr.SetSourceRGB (1.0, 1.0, 1.0); cr.Operator = Operator.Source; cr.Paint (); + fakeAlphaFailed = true; } } if (!transientAllocation.IsEmpty) { // is not empty only if we have a valid target @@ -173,16 +175,23 @@ protected override bool OnDrawn (Context cr) cr.SetSourceRGB (1.0, 1.0, 1.0); cr.Operator = Operator.Source; cr.Paint (); + fakeAlphaFailed = true; } } } cr.Restore (); - cr.LineWidth = scale > 1 ? 2 : 1; + cr.LineWidth = scale > 1 && !fakeAlphaFailed ? 2 : 1; var bounds = new Xwt.Rectangle (cr.LineWidth / 2, cr.LineWidth / 2, w - cr.LineWidth, h - cr.LineWidth); var calibratedRect = RecalibrateChildRectangle (bounds); - // Fill it with one round rectangle - RoundRectangle (cr, calibratedRect, radius); + + // without any alpha support, we fill the whole window + if (fakeAlphaFailed) { + cr.Rectangle (bounds.X, bounds.Y, bounds.Width, bounds.Height); + cr.SetSourceRGB (BackgroundColor.R, BackgroundColor.G, BackgroundColor.B); + cr.FillPreserve (); + } else // Fill it with one round rectangle + RoundRectangle (cr, calibratedRect, radius); // Triangle // We first begin by positionning ourselves at the top-center or bottom center of the previous rectangle @@ -192,20 +201,25 @@ protected override bool OnDrawn (Context cr) // We draw the rectangle path DrawTriangle (cr); - // disable alpha if we are out of parents bounds without real alpha support - // otherwise we would get artifacts when the popup sesizes (root window will - // contain the popoup - from the previous drawing - shining through our - // new background with alpha) - if ((!supportAlpha && withRoot)) { + if (fakeAlphaFailed) { cr.SetSourceRGB (170d / 255d, 170d / 255d, 170d / 255d); - cr.StrokePreserve (); - cr.SetSourceRGB (BackgroundColor.R, BackgroundColor.G, BackgroundColor.B); - cr.Fill (); + cr.Stroke (); } else { - cr.SetSourceRGBA (0.0, 0.0, 0.0, 0.2); - cr.StrokePreserve (); - cr.SetSourceRGBA (BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, BackgroundColor.A); - cr.Fill (); + // disable alpha if we are out of parents bounds without real alpha support + // otherwise we would get artifacts when the popup resizes (root window will + // contain the popoup - from the previous drawing - shining through our + // new background with alpha) + if ((!supportAlpha && withRoot)) { + cr.SetSourceRGB (170d / 255d, 170d / 255d, 170d / 255d); + cr.StrokePreserve (); + cr.SetSourceRGB (BackgroundColor.R, BackgroundColor.G, BackgroundColor.B); + cr.Fill (); + } else { + cr.SetSourceRGBA (0.0, 0.0, 0.0, 0.2); + cr.StrokePreserve (); + cr.SetSourceRGBA (BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, BackgroundColor.A); + cr.Fill (); + } } return base.OnDrawn (cr);