Units and resolution independence for gtk+



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.




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]