Extended Layout Summary

GTK+ finally has been branched for the next release cycle, which means
that features can be added. So it seems to be a good time to descibe the
extended layout patches I've created during this Summer of Code:

  * http://live.gnome.org/MathiasHasselmann/NewLayoutManager
  * http://bugzilla.gnome.org/101968



Widgets have at least two different kinds of size requisition:

  * the absolutely minimum size they can deal with
  * the natural size they prefer

GTK+ currently only offers one kind of size requisition expressed by
the "size-request" signal. Interpretation of this signal differs among
application authors, but also within the toolkit: Sometimes the values
returned by the size-request signal are considered the minimum size,
sometimes they are considered the preferred size. 

Just one result of this ambivalence I've seen recently:


Left of the combo box there also is a text entry which shows up as white
artifact above the "s" of the word "days". The problem here is, that the
text entry got some size request assigned to get some reasonable default
size - "size-request" interpreted as "natural size". This of course
leads to an unreasonable minimum size of the right pane, when disabling
the "shrink" size property of the right pane - "size-request" used as
"minimum size". So the "shrink" property was enabled, leading to this
unpleasant result, when reducing the pane's size too much.


The solution to this problem is simple: Interpret the result of the
"size-request" signal as absolutely minimum size and introduce a new
function for expressing the natural size of a widget.

When a container widget got more space allocated than requested, it
considers the difference between natural and requested size of its
children to distribute that additional space, in relation to the child's
difference between natural and minimum-size. Let's use an example for

    Assume we have a container with two children. Both children request
    a_min = b_min = 50 pixels as minimum size. The first child announces
    a_nat = 100 pixels, the second announces b_nat = 200 pixels as
    natural size.

    This gives a requested size of c_min = 100 pixels, and a natural
    size of 300 pixels (c_nat) for the container. Now the container gets
    allocated at a size of 200 pixels (c_cur). This are 100 pixels to
    distribute (c_gap).

    So the first child gets:

      a_cur = a_min + c_gap * (a_nat - a_min) / (c_nat - c_nat) 
            = 50    + 100   * 50              / 200
            = 75 pixels.

    The second child gets:

      b_cur = b_min + b_gap * (b_nat - b_min) / (c_nat - c_nat) 
            = 50    + 100   * 150             / 200
            = 125 pixels.

Widgets with "expand" property only get expanded when the allocated
space is larger than the sum of all children's natural size:
c_cur > c_nat.

This definition of natural size introduces a new invariant:

        natural-size >= size-request

This invariant should ensure, that size groups keep working, since size
groups work by modifying the result of "size-request" calls.



Over the time it became clear that the widget layout system of the GTK
has some deficit. The problems my patches address are:

  * height-for-width/width-for-height negotiation
  * expression of natural size

Initially I also planed to implement baseline alignments, but that task
turned out being too hard to solve for that limited time Summer of Code

Height-for-width and width-for-height negotiation becomes necessary
when placing for instance wrapping text: Too allocate space for a widget
the toolkit has to know the widgets size requisition. For finding that
requisition the label needs to wrap its text. For wrapping text the
widget has to know much much space it gets allocated. The cat bites
its own tail.


To work around the problem, functions like gtk_label_set_width_chars
have been introduced. In a proper solution container widgets are biased
whether to layout children horizontally or vertically and accordingly
practise width-for-height, or height-for-width negotiation when dis-
tributing the space they got allocated. To get size negotiation only
small modifications to the layout mechanism of GTK+ are required:

Currently we have:

  * size-request for container:

      * "Sum up" size-request results for each visible child.

  * size-allocate for container:

      * Distribute space by considering size-request and expand
        properties of the children.

Size negotiation implies:

  * size-request for container:

      * "Sum up" size-request results for each visible child.

  * size-allocate for container:

      * Distribute space by considering __size-negotiation__ and
        expand properties of the children. Use size-request, when
        size-negotiation is not available.

Trivial change, big effect.

I have to admit, that I have to check the impact of my size-negotiation
code on size-groups, as I forgot to define an invariant similar to the
natural-size invariant. Just realized that, when writing this up.


The ideas above are implemented in the "extended-layout" branch of
GTK+.  For easy merging I've splitted the final patch of that branch
into several smaller patches, attached to Bug #101968. Well, I planed
to attach to #104188 - well, but you know: Concentration can be a
problem when doing exciting stuff.


To be minimally invasive it was decided to implement extended layout
information as interface. This patch introduces that interface.

The interface doesn't list any prerequisites since not only widgets,
but also table columns, cell renderers, canvas items and so on can
provide extended layout information.

  gtk_extended_layout_get_features (GtkExtendedLayout *layout);

This function describes which extended layout features are supported by
the widget. A function is used to report the features, instead of
checking the vtable entries for for NULL, for two reasons:

  * Setting vtable entries to NULL is nearly impossible for custom
    widgets implemented on top of a language binding.

  * The feature set of container widgets depends on their contents.
    There is no way for GtkBin to practice size-negotiation, when its
    child doesn't support it.

  gtk_extended_layout_get_height_for_width (GtkExtendedLayout *layout,
                                            gint               width);
  gtk_extended_layout_get_width_for_height (GtkExtendedLayout *layout,
                                            gint               height);

Those two functions provide size-negotiation: The container widget tells
how much space it got in one direction, the child returns how much space
it wants for the other direction under that premise.

  gtk_extended_layout_get_natural_size (GtkExtendedLayout *layout,
                                        GtkRequisition    *requisition);

This function allows a widget to report how much space it'd ideally use.

  gtk_extended_layout_get_padding (GtkExtendedLayout *layout,
                                   GtkBorder         *padding);

Some container widgets put a padding around their children, take for
instance GtkButton or GtkAlignment. So for calculating size padding
information has to be available. Ideally this function would just be
a new virtual method of GtkBin, as currently GtkBin is the only widget
using that information to calculate its natural size. Unfortunately the
vtable of GtkBin has no padding slots.

  gtk_extended_layout_get_baselines       (GtkExtendedLayout  *layout,
  gtk_extended_layout_set_baseline_offset (GtkExtendedLayout  *layout,
                                           gint                offset);
  gtk_extended_layout_get_single_baseline (GtkExtendedLayout  *layout,
                                           GtkBaselinePolicy   policy);

Those functions were added for baseline alignment, and most probably
should be omitted for now, as there is no baseline alignment yet.

Johan Dahlin already suggested to put those functions into an additional
namespace to avoid name conflict in the bindings. My take on this: The
functions already are in a namespace: "gtk_extended_layout". Adding
additional namespace to the method part of those function names would
quite increase the namespace noise in the C code that has to implement
those features. When bindings risk to get namespace collisions due new
interfaces, then their concept for supporting GObject interfaces is
seriously broken.


This patch implements the GtkExtendedLayout interface for GtkBin. It
implements the interface by forwarding (and tweaking) the information
its child provides. In that spirit its feature set is found by inter-
secting the feature set of the child with the feature set GtkBin can

Guess I already found a bug in that patch: get_width_for_height and
get_natural_size forget to apply padding to the sizes returned by the
child. With some di


This patch moves the separate padding variables of GtkAlignmentPrivate
into a GtkBorder variable. What looks like cosmetic on a first look is
needed to make the next patch (0004-gtkalignment) more reasonable.


This patch implements the extended layout interface for GtkAlignment.
Most features are inherited from GtkBin. It just adds padding support.


This patch implements the extended layout interface for GtkButton.
As for GtkAlignment only padding support is added. All other features
are inherited from GtkBin.


This patch implements the extended layout interface for GtkFrame.
As for GtkAlignment and GtkButton only padding has to be added.


This patch implements the extended layout interface for GtkCellView.
The only feature it supports is natural size.

It modifies the "size-request" handler to collect natural-width
information for the widget's cells. As fall back the requested size
is used. The "size-allocate" handler got two modifications:

First the separate PACK_START and GTK_PACK_END are joined, as they were
identical, expect for this statement:

  if (info->pack == GTK_PACK_START)


  if (info->pack == GTK_PACK_END)

After that, the natural size algorithm described above was applied.


This implements the extended layout interface for GtkCellRendererText.
The only feature supported is natural size.

Natural size is calculated by temporarily reseting the renderer's
ellipsize property, and calculating the size request after that change.

The get_size function that calculates the size request needs a reference
to the cell's owner widget for operation, whereas the get_natural_size
has no chance to get it. Assuming that each get_size call is directly
paired with a get_natural_size call, the get_size call stores the
widget, which it got passed, so that get_natural_size reuse it.


This implements the extended layout interface for GtkScrolledWindow.
The only feature supported is natural size.

To retrieve natural size, the patch modifies the old "size-request"
handler to accept an consider_natural_size flag. When that flag is set
and the child widget supports the natural size feature, get_natural_size
is used to calculate the size requisition, otherwise size_request is
called for the child. The new "size-request" handler calls the old one,
passing "FALSE" for the flag. The natural_size function calls that
modified function with "TRUE".


This implements the extended layout interface for GtkSocket and GtkPlug.

When socket and plug exist in the same process, the socket directly
calls the plug's extended layout functions to implement extended layout.
When existing in different processes, the plug announces its natural
size by setting the "_GTK_NATURAL_SIZE" window property. The plug
retrieves its natural size by forwarding its child's information.


This implements extended layout for GtkTable and GtkTableColumn.

The GtkTableColumn implements natural size by resembling the scheme
used for GtkScrolledWindow. To avoid needless recalculations it caches
the natural width it found - similiar to the minimum size. For storing
that variable GtkTableColumn had to be introduced.

GtkTable supports natural size, by accumulating the natural width of its
columns, and by applying the natural size algorithm described above on
size allocation.


Currently GtkLabel supports ellipses only for horizontal, non-wrapping
text. Under this premise natural size support for labels would show
nearly no effect.

This patch, cooked during GUADEC, does the maths to support ellipses for
wrapped and rotated text. Didn't find a better way, but introducing a
boolean "full-width" property for overruling "char-width", "max-char-
width" and the infamous look-at-screen-size-for-wrapping hack. 

I am not proud of the "full-width" property, so I'd welcome a way to get
rid of it.


This implements extended layout for GtkLabel. This patch does the full
extended layout show: All features supported. Ok, height-for-width only
make sense for for horizontal labels, and width-for-height only for
vertical ones.

Size negotations works by calling restricting the internal PangoLayout
to the requested size and then checking the pixel size of the layout.
Natural size works by temporarily disabling ellipses. Baselines are
retrieved by inspecting each the ascent of each PangoLayoutLayout.


Extends tests/testellipsise to verify the combined results of the
previous two patches.


This implements extended layout for GtkHBox and GtkVBox.

For size-request and get_natural_size the scheme of GtkScrolledWindow is
reused, but this time a callback is used instead of a flag. This change
was chosen for code simplicity.

For size allocation the nearly identical GTK_PACK_START and GTK_PACK_END
loops are merged. When recalling properly the only difference of those
complex loops could be condensed to this trivial statement:

+          if (GTK_PACK_START == packing)
+            y = allocation->y + border_width;
+          else
+            y = allocation->y + allocation->height - border_width;

After that the usual natural size algorithm has been applied.

When calculating natural sizes for children of GtkHBox, width-for-height
negotiation is preferred over static natural size information. Same for
GtkVBox, just that height-for-width negotiation is used here.


This implements extended layout for GtkTable. Natural size information
is collected during gtk_table_size_allocate_pass1. After that the scheme
from GtkScrolledWindow is used to calculate natural size and requested

If feel quite confused for now, as I cannot see my size-negotation work
in this patch. Only see the orientation property I've added for that
purpose. Have to inspect my local git repo to find that work. Hopefully
I didn't wipe it by accident.


This patch provides the extensive test suite I used to test my code.


This file contains the change log of the extended-layout branch.


Some initial documentation.


So, that's all. Hope this information is helpful for review. 
I feel exhausted for now.

Mathias Hasselmann <mathias hasselmann gmx de>

Attachment: signature.asc
Description: Dies ist ein digital signierter Nachrichtenteil

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