Units and resolution independence for gtk+

From: David Zeuthen <david fubar dk>

To: gtk-devel-list gnome org

Subject: Units and resolution independence for gtk+

Date: Wed, 06 Aug 2008 17:07:10 -0400

Hey, Many years ago jrb did a writeup about units http://mail.gnome.org/archives/gtk-devel-list/2003-February/msg00009.html e.g. to make apps avoid using hard coded pixel values. The past few days I've been working on implementing something similar to this in GTK+. MOTIVATION My main motivation was primarily the need to scratch an itch - one of the laptops I own (incidentally mclasen has a similar one) has a 17" panel with 1920x1200 resolution. While it's nice with high resolution, everything is so tiny. Just bumping up the font size won't work; then all my scrollbars and other widgets look out of place; they're too small compared to the font. In the future we're going to see more high-DPI displays. Another motivation is that I think resolution independence ("RI") should be default in the next major version of gtk+ (see below for reasons why we can't turn it on by default in gtk 2.x) - having a shiny feature like RI sounds to me like an excellent selling point for gtk 3.0 [1]. HOW IT WORKS A new macro GTK_UNIT_EM (and it's companion GTK_UNIT_ONE_TWELFTH_EM) is introduced. This allows application to do e.g. hbox = gtk_hbox_new (FALSE, GDK_UNIT_EM (1)); instead of hbox = gtk_hbox_new (FALSE, 12); Also, a new type GdkUnit (and it's unsigned companion GdkUUnit) is introduced. It's typedef'ed to gint. Like jrb proposed some high bits are cannibalized to tag that the given value is an em and not a pixel value. Also, some users of pixel values are already using -1 and G_MAXINT as sentinels so bit 31 is off the table. Thus, bit 30 is used to specify it's an em and if so, bit 29 is used to specify the sign. A little convoluted but it works. Down the road one can add GDK_UNIT_MM GDK_UNIT_INCH etc. though I've yet to seen a valid use case for physical units. I think we should just avoid them. Then there's a new macro GDK_UNIT_TO_PIXEL that will convert a GdkUnit to pixel sizes [2]. The main idea is that widget implementations store GdkUnit's instead of gint's and use GDK_UNIT_TO_PIXEL in it's implementation (size_request, expose etc.). Now, to preserve backwards compatibility, all getters should return pixel values. This is achieved with using GDK_UNIT_TO_PIXEL in the getter. For property getters, gdk_value_set_unit() does the appropriate magic to ensure this. In addition, all widgets that provide C getters, an additional _unit() function is added, e.g. in addition to gtk_expander_get_spacing() there's also gtk_expander_get_spacing_unit() and so forth. For properties, there's gtk_widget_get_style_unit() and gtk_object_get_unit() where the latter is a pendant to g_object_get(). All setters are also changed to use GdkUnit instead of gint. There's also a new type GdkParamSpecUnit type, the use is typically gtk_widget_class_install_style_property (widget_class, - g_param_spec_int ("xspacing", - P_("XSpacing"), - P_("Extra spacing applied to the width of a progress bar."), - 0, G_MAXINT, 7, - G_PARAM_READWRITE)); + gdk_param_spec_unit ("xspacing", + P_("XSpacing"), + P_("Extra spacing applied to the width of a progress bar."), + GDK_UNIT_ONE_TWELFTH_EM (7), + G_PARAM_READWRITE)); So far so good. It get's complicated because parts of gtk+ has poor data hiding, e.g. GtkContainer exposes border_width as a public member. Also, some of these fields are 16-bit quantities. The solution here is twofold 1. for 16-bit quantities store the GdkUnit in private data and store the pixel value in public field - but we need to update this pixel value value when units change, hence a new ::unit_changed signal 2. audit all users of public fields and make them use units if appropriate (not a lot of these) Now, because of 2. we can't unfortunately turn this on by default (can't really audit/change all users of gtk+). So for gtk 2.x the application will need to turn the RI feature on [3] - it's an opt-in feature. Thus if RI is not turned on the GDK_UNIT_EM and friends will assume 1em=12px and return the pixel value directly. Thus, *everything* will be stored in pixel values and effectively there's no change whatsoever. So it's safe to commit something like this to stable gtk 2.x. PATCH There's a patch against trunk (revision 21025 from around 1pm eastern today) here http://people.freedesktop.org/~david/gtk-resolution-indepence-r21025-20080806-1315EDT.patch Notes on the patch - It's huge! 151 files changed, 4498 insertions(+), 1777 deletions(-) It weighs 518K. However, most changes are of the form [4] - range->min_slider_size = 1; + range->min_slider_size = GDK_UNIT_ONE_TWELFTH_EM (1); + usage of GDK_UNIT_TO_PIXEL to resolve to pixels + conversion to use gdk_param_spec_unit. E.g. most of the patch is trivial. - Part of of the patch deals with storing units in private data. For GtkTable and GtkBox this was really unpleasant; had to mirror all the childs etc. as private structures. Ugh. But we need something like this work anyway as part of the gtk3 plan for sealing off access to private data. - The gtkrc parser integration is lame; it currently uses ! as a prefix to mean em. It should accept e.g. "0.8em" instead of "!0.8". Shouldn't be too hard, I just didn't like hacking the rc parser right now. Here's some version of Gilouche's gtkrc ported http://people.freedesktop.org/~david/Gilouche-RI-gtkrc but note that the clearlooks theme engine got a bug with drawing radio buttons. The Raleigh engine is fine. - I have a small patch to gtk-doc to print out e.g. Default value: 0.2 em instead of Default value: 2474784784 - GdkScreen grew a "unit-changed" signal - docs - need to add docs for all new symbols - need a porting guide DEMO Here's a screencast showing the feature - http://people.freedesktop.org/~david/gtk-resolution-indepence.ogg Here are some screenshots - http://people.freedesktop.org/~david/file-chooser-em5.png - http://people.freedesktop.org/~david/file-chooser-em10.png - http://people.freedesktop.org/~david/file-chooser-em20.png PROPOSAL My proposal is to merge something like this for gtk 2.x in the near future and make it mandatory in gtk 3.0. Flames, praises, thoughts? Thanks, David [1] : FWIW, I'm firmly in the "gtk 3.0 without compelling features is suicide" camp. But this is not the thread to discuss politics. Thanks. [2] : GDK_UNIT_TO_PIXEL probably needs to take a GdkScreen (and maybe even a position given multiple monitors) as well. [3] : Using gdk_unit_enable_resolution_independence() before gtk_init(). In fact apps probably only want this if they've ported to using RI themselves too. [4] : Interesting factoid: Pixel sizes are hardcoded some 400-500 places in the gtk+ source tree.