Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

[Gtk] Add Popover transparency without alpha support #587

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions Xwt.Gtk/Xwt.GtkBackend/GtkWorkarounds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 (!Platform.IsMac && !Platform.IsWindows && !(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<KeyboardShortcut>
Expand Down
113 changes: 95 additions & 18 deletions Xwt.Gtk/Xwt.GtkBackend/PopoverBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,22 +127,71 @@ 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);

cr.Save ();

// 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
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) {
if (!cr.DrawWindow (RootWindow, x, y, w, h, Operator.Source)) {
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
// 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 ();
fakeAlphaFailed = true;
}
}
}
cr.Restore ();

cr.LineWidth = GtkWorkarounds.GetScaleFactor (Content) > 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
Expand All @@ -152,14 +201,26 @@ protected override bool OnDrawn (Context cr)
// We draw the rectangle path
DrawTriangle (cr);

// We use it
if (supportAlpha)
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 ();
if (fakeAlphaFailed) {
cr.SetSourceRGB (170d / 255d, 170d / 255d, 170d / 255d);
cr.Stroke ();
} else {
// 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);
}
Expand Down Expand Up @@ -242,10 +303,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 ();
Expand Down Expand Up @@ -304,6 +376,11 @@ void HandleParentFocusInEvent (object o, FocusInEventArgs args)
Hide ();
}

void HandleParentExposeEvent (object o, EventArgs args)
{
popover.QueueDraw ();
}

public void Hide ()
{
popover.Hide ();
Expand Down