New Layout Manager for GTK+

The layout system of GTK+ shows its limitations when using labels with long text which shall wrap automatically in resizable layout, or when using ellipsized labels together with expanding widgets in boxes. Havoc Pennington describes the natural size problem in detail on his blog. There also are some quite popular tickets in Bugzilla documenting the height-for-width issue (#104188, #101968). Solutions for this problems include width-for-height, height-for-width and natural size management. The algorithms are documented, but for whatever reason nobody has taken the challenge yet to implement them in GTK+.

Source Code

The feature is developed in the extended-layout branch of GTK+: http://git.gnome.org/cgit/gtk+/?h=extended-layout

Status Reports

Status reports can be found on by weblog: http://taschenorakel.de/tags/extendedlayout/

Natural Size

attachment:natural-size.png

Both buttons are placed within a horizontal box. Left button just is told to fill its space, the right button additionally is allowed to expand. As the left button additionally allows ellipses to be added to its label, its size is reduced much more than expected. This happens because only requested (minimal) size of a widget is considered to assign space non-expanding widgets. Current layout algorithms of GTK+ work approximatly like this (ignoring numeric overflows for easy of reading):

  requested(w) := "size requested by w"
  allocated(w) := "size assigned to w"
  rgap(w) := allocated(w) - requested(w)

  expanded(p) := { c | c in children(p) and expand(c) }
  regular(p) := { c | c in children(p) and not expand(c) }
  requested(p) := sum { requested(c) | c in children(p) }

  expansion(c, p) := | rgap(p) / ||expanded(p)|| : c in expaned(p)
                     | 0                         : otherwise

  allocated(c, p | c in children(p)) := requested(c) + expansion(c, p)

Or in pseudo-code:

  n_expanded = 0
  requested = 0

  for c in p.children:
    if c.expand:
      ++n_expaned

    requested += c.requested
    c.size = c.requested

  gap = (p.size - requested)

  for c in p.children:
    if c.expand:
      c.size += gap / n_expended

Supporting natural size would introduce those changes:

  natural(w) := "natural size of w"

  ngap(w) := min { allocated(w), natural(w) } - requested(w)
  rgap(w) := allocated(w) - ngap(w) - requested(w)

  natural(p) := sum { natural(c) | c in children(p) }
  natural_expansion(c, p) := ngap(p) * natural(c) / natural(p)

  allocated(c, p | c in children(p)) := requested(c) + natural_expansion(c, p) + expansion(c, p)

Height-for-Width

attachment:height-for-width.png

Baseline Alignment

attachment:baseline-alignment.png

For high quality layout you wish to align all upon a common baseline, that is all letters of a row should "sit" on the same line. Currently developers use GtkAlignment to simulate such a behavior, but results often are imperfect - considering all the information Pango provides we can do better: After calculating horizontal placement of all children the baseline of all children is queried. Vertical paddings are injected to ensure all children meet at the same baseline.

  baseline(p) := max { baseline(c) | c in children(p) }
  y(c) := baseline(p) - baseline(c)

This algorithm considers only one base-line per container row, so when a child provides serval potential baselines, like for instance multi-lined labels, one of this baseslines has to be choosen. For instance the container widget could get a baseline-alignment property describing wether to use the first, the last or some average baseline. By default this property would be set to "none" for maximum compatiblity.

Proposed API

Widgets willing to provide extended layout information implement the following GObject interface:

/*< flags >*/
enum _GtkExtendedLayoutFeatures 
{
  GTK_EXTENDED_LAYOUT_WIDTH_FOR_HEIGHT = (1 << 0), 
  GTK_EXTENDED_LAYOUT_HEIGHT_FOR_WIDTH = (1 << 1),
  GTK_EXTENDED_LAYOUT_NATURAL_SIZE  =    (1 << 2),
  GTK_EXTENDED_LAYOUT_BASELINES =        (1 << 3)
};

struct _GtkExtendedLayoutIface 
{
  /** 
   * Query which extended layout features are supported 
   */
  GtkExtendedLayoutFeatures (*get_features)     (GtkExtendedLayout *layout);

  /** 
   * Query which height would be required when space of the given 
   * width would be assigned to the extended layout item.
   */
  gint                      (*height_for_width) (GtkExtendedLayout *layout,
                                                 gint               width);

  /** 
   * Query which width would be required when space of the given 
   * height would be assigned to the extended layout item.
   */
  gint                      (*width_for_height) (GtkExtendedLayout *layout,
                                                 gint               height);

  /** 
   * Query the natural (preferred) size of the layout item.
   */
  void                      (*natural_size)     (GtkExtendedLayout *layout,
                                                 GtkRequisition    *req);

  /** 
   * Query the baselines of the layout item. If the item contains text,
   * for instance, those baselines are the baselines of its text lines.
   */
  void                      (*baselines)        (GtkExtendedLayout *layout,
                                                 gint             **baselines,
                                                 gint              *length);
};

Schedule

1. Test-Suite and Mockups (Until Jun 11th)

Test cases demonstrating GTK+'s current limitations and the effect of the new layout algorithms have to be written. They allow to validate correct operation of the new layout modes. The test cases would have to be able to interact with Federico's widget profiler to evaluate performance of the new layout modes. To my current knowledge the widgets to be tested would be GtkLabel, GtkAlignment, GtkBox in its variants, GtkLayoutView, GtkCellView and GtkComboBox. Some test-cases will need access to new widget and child properties not exisiting yet. Mockup code would be injected into GTK+ for making the test-case compile.

2. GtkLabel (Until Jun 25th)

GtkLabel is the most prominent widget being affected by layout management limitations and it also is capable of implementing all three layout modes therefore it shall be choosen as first implementor of GtkExtendedLayout. In parallel GtkBox would be extended to consider the new information provided by GtkLabel.

3. GtkBin (Until Jul 2nd)

Widgets extending GtkBin usually host only one child widget. Therefore they can implement GtkExtendedLayout by forwarding information retreived from their child. Additionally it would be useful to introduce an explict natural size properties at this level - but considering the many widgets extending GtkBin, the frequently abused GtkAlignment could be a better host for this property.

4. GtkBox (Until Jul 9th)

To make the extended layout forwarding of GtkBin really useful and to make prominent bins like GtkButton behave consistent regardingless if they contain just a label or also some icon, GtkBox has to use the extended layout information gathered from its child to calculate extended layout information for itself.

5. GtkTable (Until Jul 23th)

GtkBox served as pretty test-bed for implementing the new layout modes. No the knowledge gathered has to be used for making GtkTable consider extended layout information.

6. GtkCellView (Until Jul 30th)

Much of the knowledge gained with GtkBox can be used here. Main work consists in extending at least one cell renderer to provide extended layout information. Most natural the work done for GtkLabel would be transfered to GtkCellRendererText, but as far as i remember some cell renderers just embed widgets (GtkCellRendererCombo and -Toggle), so knowledge gained for GtkBin should be transfered easily.

7. GtkTreeView (Until Aug 6th)

With an ideal world the code written for GtkCellView will be easy to adopt for GtkTreeView, but GtkTreeView has gathered some legacy over the years, so I expect some stop gaps here.

8. GtkComboBox (Until Aug 13th)

Ideally the changes to GtkCellView and GtkTreeView were sufficient to let GtkComboBox (and GtkComboBoxEntry) take advantage of the extended layout information - but you never know. That why this widget gets its own week of attention.

9. GtkIconView (Until Aug 20th)

GtkIconView also uses cell renderers for its operation. As I had quite some problems with the layout management of this widget in the past I delay my work on this widget to the very end of the project: I expect to encounter several other layout bugs, before I can finish integration of the new layout modes.

Final Conclusions

This schedule demonstrates, that the project is much more ambitious than it seems on the first glance. The chart below doesn't have any buffers for unexpected problems. I hope to create those buffers by finishing steps 2, 3 and 4 quickly. Implementing the new layout modes for GtkIconView holds some risks, so I do know know yet, if this widget will be ported in time. Creation of the new unified container widget has to be delayed after all other steps and currently does not fit into the limited time window offered by the Summmer of Code, but work on it cannot be started before impact of that new layout mode on the existing container widgets is known.

| May 28 | Jun 4  | Jun 11 | Jun 18 | Jun 25 | Jul 2  | Jul 9  | Jul 16 | Jul 23 | Jul 30 | Aug 6  | Aug 13 | Aug 20
+-----------------------------------------------------+-----------------------------------------------------+--------
| (1) Test-Suite --->                                 |                                                     |
|                 | (2) Label -------->               |                                                     |
|                                   | (3) Bin ->      |                                                     |
|                                            | (4) Box ->                                                   |
|                                                     | (5) Table -------->                                 |
|                                                     |                 | (6) CV -->                        |
|                                                     |                          | (7) TV -->               |
|                                                     |                                   | (8) CB -->      |
|                                                     |                                            | (9) IV -->
+-----------------------------------------------------+-----------------------------------------------------+--------

MathiasHasselmann/NewLayoutManager (last edited 2010-03-30 19:14:30 by TobiasMueller)