GNOME Bugzilla – Bug 687842
Support partially transparent widgets
Last modified: 2013-02-12 16:03:27 UTC
We currently support transparent backgrounds, but widget rendering is still based on the painters algorithm, which means we cannot create transparent objects that are made up of multiple drawing steps, as that requires some form of transparency groups. This patchset adds support for this, both for window and no-window widgets.
Created attachment 228366 [details] [review] Add "parent widget" button to test property editor Without this its hard to access the properties of container widgets.
Created attachment 228367 [details] [review] gdk: Add gdk_window_has_alpha helper This centralizes the current checks for has_alpha_bg, which lets us extend the check later.
Created attachment 228368 [details] [review] gdkwindow: Store the implicit paint in a list This changes nothing, but lets us later have multiple implicit paints
Created attachment 228369 [details] [review] gdkwindow: Allow gdk_window_set_opacity on non-native children We now store the current opacity for all windows. For native windows we just call into the native implementation whenever the opacity changes. However, for non-native windows we implement opacity by pushing a second implicit paint that "stacks" on the existing one, acting as an opacity group while rendering the window and its children. This works well in general, although any native child windows will of course not be opaque. However, there is no way to implement implicit paint flushing (i.e. draw the currently drawn double buffer to the window in order to allow direct drawing to the window). We can't flush in the stacked implicit paint case because there is no way to get the right drawing behaviour when drawing directly to the window. We *must* draw to the opacity group to get the right behaviour. We currently flush if: * A widget disables double buffering * You call move/resize/scroll a window and it has non-native children during the expose handler In case this happens we warn and flush the outermost group, so there may be drawing errors.
Created attachment 228370 [details] [review] Support opacity for all widgets This adds gtk_widget_get/set_opacity, as well as a GtkWidget.opacity property. Additionally it deprectates gtk_window_get/set_opacity and removes the GtkWindow.opacity property (in preference for the new identical inherited property from GtkWidget, which should be ABI/API compat). The implementation is using the new gdk_window_set_opacity child window support for windowed widgets, and cairo_push/pop_group() bracketing in gtk_widget_draw() for non-window widgets.
Review of attachment 228366 [details] [review]: sure
Review of attachment 228367 [details] [review]: ok
Review of attachment 228368 [details] [review]: ok
Review of attachment 228369 [details] [review]: ::: gdk/gdkwindow.c @@ +11124,2 @@ * + * For child windows this function only work You didn't complete your thought here.
Review of attachment 228370 [details] [review]: ::: gtk/gtkwidget.c @@ +13724,3 @@ + * of the opacity parameter are clamped to the [0,1] range.). + * This works on both toplevel widget, and child widgets, although there + * are some limitiations: Instead of 'Values of the opacity parameter' I would just say 'Opacity values'. And you probably mean 'toplevel windows' instead of 'toplevel widget'. Finally, 'limitations'. @@ +13737,3 @@ + * For child widgets without a window only the widget itself and child widgets + * without windows are made opaque. Any child window at all inside the widget + * will be opaque. This sounds confusing to me. so many child widgets and windows. And do you mean 'translucent' when you say 'opaque' ? @@ +13763,3 @@ + if (alpha == priv->alpha) + return; + this is the second place where we take opacity as a double, and store alpha as a byte - shouldn't we avoid the rounding errors and just store the double ? In particular, considering there's a getter which will currently give you back a quantized value thats different from what you passed in the setter. ::: gtk/gtkwindow.c @@ +2689,3 @@ * * Since: 2.12 + * Deprecated: 3.8: Use gtk_widget_set_opacity instead. Add () to get the function name linkified in the docs. @@ +2708,3 @@ * * Since: 2.12 + * Deprecated: 3.8: Use gtk_widget_get_opacity instead. here too
Review of attachment 228369 [details] [review]: ::: gdk/gdkwindow.c @@ +11124,2 @@ * + * For child windows this function only work for non-native windows.
Review of attachment 228370 [details] [review]: ::: gtk/gtkwidget.c @@ +13737,3 @@ + * For child widgets without a window only the widget itself and child widgets + * without windows are made opaque. Any child window at all inside the widget + * will be opaque. Yeah, the terminology is a bit confusing. But opaque is right, as in, if you make a no-window widget such as a GtkHBox transparent and it happens to have some windowed widget as a child (say an EventBox) that widget will not be translucent as the rest of the child widgets are, because for no-window widgets we implement translucency by just modifying the cairo_t that gets propagated in the draw() signal by the container widgets. @@ +13763,3 @@ + if (alpha == priv->alpha) + return; + Storing a 64bit value for every widget for something that is practically never gonna support anything beyond 255 levels seems wasteful to me, and it also leads to issues with comparisons to 1.0 to avoid the slow paths, as equality comparisons with floats are dangerous.
A few comments: (1) The first commits should have just been pushed to master, they don't depend on this feature. (2) I don't think gtk_widget_set_opacity() should be public API. We want the styling code to use it, not applications. (3) Public properties for color or alpha values should use doubles. I don't care much about how they're represented internally - you can do priv->alpha = round (255 * g_value_get_double(value)) for all I care, but just like in the Cairo world, the public API should look sane and consistent. But as long as this feature isn't gonna work reliably and in all the cases we care about, I don't think we want it in. In particular because we don't have a good reason to have it yet.
Attachment 228366 [details] pushed as c94002f - Add "parent widget" button to test property editor Attachment 228367 [details] pushed as 8a40d8f - gdk: Add gdk_window_has_alpha helper
Created attachment 235206 [details] [review] gdkwindow: Store the implicit paint in a list This changes nothing, but lets us later have multiple implicit paints
Created attachment 235207 [details] [review] gdkwindow: Allow gdk_window_set_opacity on non-native children We now store the current opacity for all windows. For native windows we just call into the native implementation whenever the opacity changes. However, for non-native windows we implement opacity by pushing a second implicit paint that "stacks" on the existing one, acting as an opacity group while rendering the window and its children. This works well in general, although any native child windows will of course not be opaque. However, there is no way to implement implicit paint flushing (i.e. draw the currently drawn double buffer to the window in order to allow direct drawing to the window). We can't flush in the stacked implicit paint case because there is no way to get the right drawing behaviour when drawing directly to the window. We *must* draw to the opacity group to get the right behaviour. We currently flush if: * A widget disables double buffering * You call move/resize/scroll a window and it has non-native children during the expose handler In case this happens we warn and flush the outermost group, so there may be drawing errors.
Created attachment 235208 [details] [review] Add gtk_widget_(un)register_window This replaces the previously hardcoded calls to gdk_window_set_user_data, and also lets us track which windows are a part of a widget. Old code should continue working as is, but new features that require the windows may not work perfectly. We need this for the transparent widget support to work, as we need to specially mark the windows of child widgets. Iniital register_windows patch Flush out register_window
Created attachment 235209 [details] [review] Add gtk_widget_get/set_opacity This adds gtk_widget_get/set_opacity, as well as a GtkWidget.opacity property. Additionally it deprectates gtk_window_get/set_opacity and removes the GtkWindow.opacity property (in preference for the new identical inherited property from GtkWidget, which should be ABI/API compat). The implementation is using the new gdk_window_set_opacity child window support for windowed widgets, and cairo_push/pop_group() bracketing in gtk_widget_draw() for non-window widgets.
Created attachment 235210 [details] [review] Add widget transparency test to testgtk
Created attachment 235211 [details] [review] TextHandle: Don't draw handles if not visible When calling gtk_widget_draw() on the entry gtk_cairo_should_draw_window() will return TRUE for all windows. This is used when rendering a widget to somewhere other than the screen, and its now used for transparent widgets. This caused the texthandle to always draw itself and terminate the draw handler for the entry. Instead we now only draw the markers when really visible, plus we return FALSE to avoid stopping the entry drawing.
This new series of patches works in more or less all cases without native child windows, since it relies on the support for being able to render all subwindows similarly to gtk_widget_draw(). I had to fix GtkTextHandle to make this work for the entry, but that kind of bug is also a bug since before and should be fixed. I'd like some feedback on this, but i think it should be basically ok for real use. We also need to hook it up to the css machinery, but I disagree with benjamin that is should only be availible there. You may very well want to use this for e.g. widget cross fading, in say a GtkStack like widget. We probably want to support both css and the app to be able to set the opacity though, maybe we should just multiply the two opacities in gtkwidget.c.
Review of attachment 235211 [details] [review]: Makes sense
> We probably want to support both css and the app to be able to set the opacity > though, maybe we should just multiply the two opacities in gtkwidget.c. Agreed
Review of attachment 235208 [details] [review]: Some squashing leftovers in the commit message. Might also want to address https://bugzilla.gnome.org/show_bug.cgi?id=111664 while we are at it ? ::: gtk/gtkwidget.c @@ +396,3 @@ */ GdkWindow *window; + GList *registred_windows; msissing an e here: registered ::: gtk/gtkwidget.h @@ +634,3 @@ + GdkWindow *window); +void gtk_widget_unregister_window (GtkWidget *widget, + GdkWindow *window); GDK_AVAILABLE_IN_3_8
Review of attachment 235208 [details] [review]: re bug 111664, I'm not sure how such a getter would be named.
Created attachment 235307 [details] [review] Add singleton for css number 1.0 This will be nice as this is will be the default for opacity.
Created attachment 235308 [details] [review] css: Support opacity
Review of attachment 235308 [details] [review]: ::: gtk/gtkwidget.c @@ +14009,3 @@ + * + * For child widgets it doesn't work if any affected widget has a native window, or + * disables double buffering. Is this still the case ?
Review of attachment 235308 [details] [review]: ::: gtk/gtkwidget.c @@ +14009,3 @@ + * + * For child widgets it doesn't work if any affected widget has a native window, or + * disables double buffering. Yes, once you get an X window in there is no way to make that transparent really.
Created attachment 235364 [details] [review] gdkwindow: Store the implicit paint in a list This changes nothing, but lets us later have multiple implicit paints
Created attachment 235365 [details] [review] gdkwindow: Allow gdk_window_set_opacity on non-native children We now store the current opacity for all windows. For native windows we just call into the native implementation whenever the opacity changes. However, for non-native windows we implement opacity by pushing a second implicit paint that "stacks" on the existing one, acting as an opacity group while rendering the window and its children. This works well in general, although any native child windows will of course not be opaque. However, there is no way to implement implicit paint flushing (i.e. draw the currently drawn double buffer to the window in order to allow direct drawing to the window). We can't flush in the stacked implicit paint case because there is no way to get the right drawing behaviour when drawing directly to the window. We *must* draw to the opacity group to get the right behaviour. We currently flush if: * A widget disables double buffering * You call move/resize/scroll a window and it has non-native children during the expose handler In case this happens we warn and flush the outermost group, so there may be drawing errors.
Created attachment 235366 [details] [review] Add gtk_widget_(un)register_window This replaces the previously hardcoded calls to gdk_window_set_user_data, and also lets us track which windows are a part of a widget. Old code should continue working as is, but new features that require the windows may not work perfectly. We need this for the transparent widget support to work, as we need to specially mark the windows of child widgets.
Created attachment 235367 [details] [review] Add gtk_widget_get/set_opacity This adds gtk_widget_get/set_opacity, as well as a GtkWidget.opacity property. Additionally it deprectates gtk_window_get/set_opacity and removes the GtkWindow.opacity property (in preference for the new identical inherited property from GtkWidget, which should be ABI/API compat). The implementation is using the new gdk_window_set_opacity child window support for windowed widgets, and cairo_push/pop_group() bracketing in gtk_widget_draw() for non-window widgets.
Created attachment 235368 [details] [review] Add widget transparency test to testgtk
Created attachment 235369 [details] [review] TextHandle: Don't draw handles if not visible When calling gtk_widget_draw() on the entry gtk_cairo_should_draw_window() will return TRUE for all windows. This is used when rendering a widget to somewhere other than the screen, and its now used for transparent widgets. This caused the texthandle to always draw itself and terminate the draw handler for the entry. Instead we now only draw the markers when really visible, plus we return FALSE to avoid stopping the entry drawing.
Created attachment 235370 [details] [review] Add singleton for css number 1.0 This will be nice as this is will be the default for opacity.
Created attachment 235371 [details] [review] css: Support opacity
Created attachment 235372 [details] [review] css: Add opacity reftest
Attachment 235206 [details] pushed as ada6d81 - gdkwindow: Store the implicit paint in a list Attachment 235207 [details] pushed as 4d3c77f - gdkwindow: Allow gdk_window_set_opacity on non-native children Attachment 235208 [details] pushed as 3d4cd4d - Add gtk_widget_(un)register_window Attachment 235209 [details] pushed as fa8b714 - Add gtk_widget_get/set_opacity Attachment 235210 [details] pushed as b57a2c8 - Add widget transparency test to testgtk Attachment 235211 [details] pushed as ebb84e8 - TextHandle: Don't draw handles if not visible Attachment 235307 [details] pushed as 7d21ec2 - Add singleton for css number 1.0 Attachment 235308 [details] pushed as 366b4db - css: Support opacity Attachment 235364 [details] pushed as ada6d81 - gdkwindow: Store the implicit paint in a list Attachment 235365 [details] pushed as 4d3c77f - gdkwindow: Allow gdk_window_set_opacity on non-native children Attachment 235366 [details] pushed as 3d4cd4d - Add gtk_widget_(un)register_window Attachment 235367 [details] pushed as fa8b714 - Add gtk_widget_get/set_opacity Attachment 235368 [details] pushed as b57a2c8 - Add widget transparency test to testgtk Attachment 235369 [details] pushed as ebb84e8 - TextHandle: Don't draw handles if not visible Attachment 235370 [details] pushed as 7d21ec2 - Add singleton for css number 1.0 Attachment 235371 [details] pushed as 366b4db - css: Support opacity Attachment 235372 [details] pushed as 97c2354 - css: Add opacity reftest
Created attachment 235700 [details] [review] Add gtk_widget_get/set_has_opacity_group This adds a way to get the gtk_widget_set_opacity liike behaviour of retargeting GdkWindows and exposing every child in ::draw, without actually having an alpha. This is needed if you're doing more complex things such as cross fading of widgets.
Created attachment 235734 [details] [review] Clean up the opacity handling This cleans up the internals but doesn't really change the behaviour.
Created attachment 235735 [details] [review] Add gtk_widget_get/set_has_opacity_group This adds a way to get the gtk_widget_set_opacity liike behaviour of retargeting GdkWindows and exposing every child in ::draw, without actually having an alpha. This is needed if you're doing more complex things such as cross fading of widgets.
Comment on attachment 235734 [details] [review] Clean up the opacity handling Attachment 235734 [details] pushed as 7319f29 - Clean up the opacity handling
Benjamin had some reservations on the opacity group stuff, so i split out the pure cleanup from the patch and attached a rebased patch which is more focused on the problem at hand. I also commited a stack widget in libgd, and i have a patch for it that adds cross-fading based on this patch. Will attach that patch here.
Created attachment 235738 [details] [review] GdStack: Add crossfade support
So, benjamin dislikes this patch because its some weird, hard to understand call that you have to magically know when to use when Gtk+ should just figure out when this was needed by itself. So, let me back up a bit and try to describe the issue as I see it, and maybe we can come up with a way forward. The issue appears when someone wants to write a container widget that treats its children in a different way than the traditional way of "each widget overpaints the previous in back-to-front order". The simplest example is to just have an alpha value and draw all the children to an offscreen buffer (via cairo_push_group()) and then draw the buffer using OPERATOR_OVER and paint_with_alpha. This is what the set_opacity() patch currently in does, but there are some cases where you need something more complicated. For instance, the code needed for the crossfade in the GdStack patch is essentially: // opacity group where we combine two things with ADD cairo_push_group (cr); cairo_set_source_surface (cr, xfade_surface, 0, 0); cairo_set_operator (cr, CAIRO_OPERATOR_ADD); cairo_paint_with_alpha (cr, 1.0 - xfade_pos); // group to merge all the child drawing into one for the ADD cairo_push_group (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); gtk_container_propagate_draw (GTK_CONTAINER (stack),child,cr); cairo_pop_group_to_source (cr); cairo_set_operator (cr, CAIRO_OPERATOR_ADD); cairo_paint_with_alpha (cr, xfade_pos); cairo_pop_group_to_source (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_paint (cr); If you were to put this code in the ::draw signal in a container things would work fine for trivial cases, but as soon as any descendant widget of the container had a GdkWindow things would break down for two reasons: 1) The container widget draw will draw on its (or its parents) GdkWindow, and such rendering will normally be clipped by a child GdkWindow. In fact, such clipping will be done by the expose machinery such that the expose region for the container widget will not even contain ares covered by child regions. 2) Even if we didn't clip, the normal gtk_container_propagate_draw() will not actually propagate into child widgets that have other windows, nor will the draw event draw to GdkWindows other than the one specified in the ExposeEvent. These will instead be draw in separate expose events later. This means that we will not get the right stack set up in order to redirect child widget drawing via cairo_push_group. The way gtk_widget_set_opacity() fixes this is that once you've set that on a GtkWidget we traverse the GtkWidget hierarchy below it, finding all the GdkWindows that could cause problems like this and set them to be completely transparent using gdk_window_set_opacity(window, 0). This way we avoid all clipping in the parent windows, and we don't get any expose events for these windows. This doesn't completely fix 2) above, so we also have to fiddle with the cairo_t to make should_draw_window() and propagate_draw() do the right thing for all child widgets. With this set up code like the above cross fade will just work. So, why don't we *always* do things like this? Then we would need no magic for enabling cross fades. The reason for this is scrolling performance. In almost all our UIs it ends up that things that scroll are opaque (i.e. no pixels from below the scrolled widget blends through), which lets us do scrolling by copying an area in the window and then just rendering the newly exposed region. It turns out that this is actually very important (for instance, gnome-documents recently had to ensure a scrolledwindow was opaque to fix a really slow scrolling problem). What gdk does to handle scrolling is to track all areas of a native window that are "layered" (i.e multple windows blend together), and whenever a window scrolls regions that are opaque gets scrolled by XCopyArea, whereas the layered areas are just re-exposed in their entierty. The way we track layered areas is by looking at information on the GdkWindow, such as the background (does it have alpha?) and the opacity (is it != 1.0?). But, in an example like the above, there is really no way for Gdk to know that the windows in the cross-faded childs are layered, in fact the container window may not even have some GdkWindow in the gdk hierarchy, so we can't even flag this on some object. If we had such a window iin the hierarchy we can make that transparent to get the right behaviour, but if not we need to traverse the Gtk hierarchy to find the right child GdkWindows to make transparent. So, IMHO we have the following options: 1) Don't allow draw signals to use push_group() style rendering, apart from what we do inside GtkWidget (i.e. gtk_widget_set_opacity). 2) Make *all* non-native GdkWindows transparent and do all hierarchy and clipping inside Gtk, allowing full drawing capabilities but disallow efficient scrolling. 3) Have a magic function that tells Gtk that this widget renders in a "fancy" way and needs to get all child windows made transparent, etc. For most things this will allow efficient scrolling, but not when inside a widget marked as fancy. This is the approach taken in the gtk_widget_set_has_opacity_group() patch. 4) Have a magic function that tells Gtk that a widget is *not* rendering in a "fancy" way, and unless that is called we make windows transparent, etc. Then we modify all the typical Gtk widgets that do have non-INPUT_ONLY widgets to set this flag, allowing us to get good performance in the "normal" case where the scrolledwindow is packed only inside typical containers.
I'd like to add that there is a compat risk with 2 and 4, as it changes how widgets are rendered in the "normal" case, so things that don't correctly handle gtk_cairo_should_draw_window() and expect multiple draws, one for each window, may break.
Another problem with 4 is that classes may derive from a class marked as not-fancy, or override the signal handler and make it fancy and they'd then need to mark it as fancy or things would break.
Created attachment 235776 [details] [review] GdStack: Add crossfade support
added new rebase of crossfade patch
Created attachment 235782 [details] [review] Add opacity_group hack This adds a way to get the gtk_widget_set_opacity liike behaviour of retargeting GdkWindows and exposing every child in ::draw, without actually having an alpha. This is needed if you're doing more complex things such as cross fading of widgets. We do this as a hack by using opacity values that round to 255 yet not really 1.0 in order to avoid having some magical API call for this mainly internal call.
New patch which "hides" the API in gtk_widget_set_opacity, as this is kinda weird, mostly-internal API which we don't want to litter the API docs with.
Attachment 235782 [details] pushed as 8b9254c - Add opacity_group hack