IUP::Manual::05_DialogLayout - Dialog layout composition
Most interface toolkits employ the concrete layout model, that is, control positioning in the dialog is absolute in coordinates relative to the upper left corner of the dialogs client area. This makes it easy to position the controls on it by using an interactive tool usually provided with the system. It is also easy to dimension them. Of course, this positioning intrinsically depends on the graphics systems resolution. Moreover, when the dialog size is altered, the elements remain on the same place, thus generating an empty area below and to the right of the elements. Besides, if the graphics systems resolution changes, the dialog inevitably will look larger or smaller according to the resolution increase or decrease.
IUP implements an abstract layout concept, in which the positioning of controls is done relatively instead of absolutely. For such, composition elements are necessary for composing the interface elements. They are boxes and fillings invisible to the user, but that play an important part. When the dialog size changes, these containers expand or retract to adjust the positioning of the controls to the new situation.
Watch the codes below. The first one refers to the creation of a dialog for the Microsoft Windows environment using its own resource API. The second uses IUP. Note that, apart from providing the specification greater flexibility, the IUP specification is simpler, though a little larger. In fact, creating a dialog on IUP with several elements will force you to plan your dialog more carefully on the other hand, this will actually make its implementation easier.
Moreover, this IUP dialog has an indirect advantage: if the user changes its size, the elements (due to being positioned on an abstract layout) are automatically re-positioned horizontally.
The composition elements includes vertical boxes (IUP::Vbox), horizontal boxes (IUP::Hbox) and filling (IUP::Fill). There is also a depth box (IUP::Zbox) in which layers of elements can be created for the same dialog, and the elements in each layer are only visible when that given layer is active.
$dialog = IUP::Dialog->new( TITLE=>'Title',child=> IUP::Hbox->new( MARGIN=>"15x15", GAP=>10, child=>[ IUP::Fill->new(), IUP::Button->new(TITLE=>"Ok", SIZE=>"40"), IUP::Button->new(TITLE=>"Cancel", SIZE=>"40"), IUP::Fill->new(), ]) );
Following, the abstract layout representation of this dialog:
IUP::Dialog | +-> IUP::Hbox | +->IUP::Fill | +->IUP::Button | +->IUP::Button | +->IUP::Fill
Because of the dynamic nature of the abstract layout IUP elements have implicit many types of size. But the native elements have only two types of size: Window and Client. The Window size reflects the bounding rectangle of the element. The Client size reflects the inner size of the window that excludes the decorations and margins. For many elements these two sizes are equal, but for many containers they are quite different. See some examples bellow.
The IUP sizes (User, Natural and Current) described bellow are all related to the Window size.
The native Client size is used only internally to reposition the elements in the abstract layout, but it is available using the CLIENTSIZE attribute.
IUP does not require that the application specifies the size of any element. The sizes are automatically calculated so the contents of each element is fully displayed. This size is called Natural size. The Natural size is calculated just before the element is mapped to the native system and every time Map is called, even if the element is already mapped.
The Natural size of a container is the size that allows all the elements inside the container to be fully displayed. Important: even if the element is invisible its size will be included in the size of its containers, except when FLOATING=Yes.
So consider the following code and its result. Each button size is large enough to display their respective text. If the dialog size is increased or reduced by the size handlers in the dialog borders the buttons do not move or change their sizes.
use IUP ':all'; my $dialog = IUP::Dialog->new( TITLE=>"IupDialog", FONT=>"Helvetica, Bold 14" ,child=> IUP::Vbox->new( child=>[ IUP::Button->new(TITLE=>"Button Very Long Text"), IUP::Button->new(TITLE=>"short"), IUP::Button->new(TITLE=>"Mid Button"), ]) ); $dialog->Show(); IUP->MainLoop;
By default the layout computation uses the Natural size of the elements to compose the layout of the dialog, but if the User size is defined then it is used instead of the Natural size, except for containers (not including the dialog) where the User size will be used only if bigger than the Natural size. For the dialog, when the User size is not defined, the Natural size is used only if bigger than the Current size, so in this case the dialog will automatically increase its size to fit all its contents, but if the Natural size is smaller then the dialog size will remains the same, i.e. the dialog will not automatically shrink its size.
The returned value for SIZE or RASTERSIZE is the Current size in IUP. It returns the native Window size of the element after the element is mapped to the native system. Before mapping, the returned value is the User size defined by SIZE or RASTERSIZE attributes if any, otherwise they are NULL.
Defining the SIZE attribute of the buttons in the example we can make all have the same size. (In the following example the dialog size was changed after it was displayed on screen)
use IUP ':all'; my $dialog = IUP::Dialog->new( TITLE=>"IupDialog", FONT=>"Helvetica, Bold 14" ,child=> IUP::Vbox->new( child=>[ IUP::Button->new(TITLE=>"Button Very Long Text", SIZE=>"50x"), IUP::Button->new(TITLE=>"short", SIZE=>"50x"), IUP::Button->new(TITLE=>"Mid Button", SIZE=>"50x"), ]) ); $dialog->Show(); IUP->MainLoop;
So when EXPAND=NO (see bellow) for elements that are not containers if User size is defined then the Natural size is ignored.
If you want to adjust sizes in the dialog do it after the layout size and positioning are done, i.e. after the dialog is mapped or after Refresh is called.
Another way to increase the size of elements is to use the EXPAND attribute. When there is room in the container to expand an element, the container layout will expand the elements that have the EXPAND attribute set to YES, HORIZONTAL or VERTICAL accordingly, even if they have the User size defined.
The default is EXPAND=NO, but for containers is EXPAND=YES.
Using EXPAND in the example, we obtain the following result:
use IUP ':all'; my $dialog = IUP::Dialog->new( TITLE=>"IupDialog", FONT=>"Helvetica, Bold 14" ,child=> IUP::Vbox->new( child=>[ IUP::Button->new(TITLE=>"Button Very Long Text"), IUP::Button->new(TITLE=>"short", EXPAND=>"HORIZONTAL"), IUP::Button->new(TITLE=>"Mid Button", EXPAND=>"HORIZONTAL"), ]) ); $dialog->Show(); IUP->MainLoop;
So for elements that are NOT containers, when EXPAND is enabled the Natural size and the User size are ignored.
For containers the default behavior is to always expand or if expand is disabled they are limited to the Natural size. As a consequence (if the User size is not defined in all the elements) the dialog contents can only expand and its minimum size is the Natural size, even if EXPAND is enabled for its elements. In fact the actual dialog size can be smaller, but its contents will stop to follow the resize and they will be clipped at right and bottom.
If the expansion is in the same direction of the box, for instance expand="VERTICAL" in the Vbox of the previous example, then the expandable elements will receive equal spaces to expand according to the remaining empty space in the box. This is why elements in diferent boxes does not align perfectly when EXPAND is set.
To reduce the size of the dialog and its containers to a size smaller than the Natural size the SHRINK attribute of the dialog can be used. If set to YES all the containers of the dialog will be able to reduce its size. But be aware that elements may overlap and the layout result could be visually bad if the dialog size is smaller than its Natural size.
Notice that in the example the dialog inicial size will be 0x0 because it is not defined. The picture shown was captured after manually resizing the dialog. So when using SHRINK usually you will also need to set the dialog initial size.
use IUP ':all'; my $dialog = IUP::Dialog->new( TITLE=>"IupDialog", FONT=>"Helvetica, Bold 14", SHRINK=>"YES" ,child=> IUP::Vbox->new( child=>[ IUP::Button->new(TITLE=>"Button Very Long Text"), IUP::Button->new(TITLE=>"short", EXPAND=>"HORIZONTAL"), IUP::Button->new(TITLE=>"Mid Button", EXPAND=>"HORIZONTAL"), ]) ); $dialog->Show(); IUP->MainLoop;
The layout of the elements of a dialog in IUP has a natural hierarchy because of the way they are composed together.
To create a node simply call one of the pre-defined constructors like IUP::Label, IUP::Button, IUP::Canvas, and so on. To create a branch just call the constructors of containers like IUP::Dialog, IUP::Frame, IUP::Vbox, and so on. Internally they all call Create to create branches or nodes. To destroy a node or branch call Destroy.
For the element to be visible Map must be called so it can be associated with a native control. Show, ShowXY or Popup will automatically call Map before showing a dialog. To remove this association call Unmap.
But there is a call order to be able to call theses functions that depends on the state of the element. As you can see from these functions there are 3 states: created, appended and mapped. From created to mapped it is performed one step at a time. Even when the constructor receives the children as a parameter Append is called internally. When you detach an element it will be automatically unmapped if necessary. When you destroy an element it will be automatically detached if necessary. So explicity or implicity there will be a call to:
$elem->Create ->>> $elem->Append ->>> $elem->Map $elem->Destroy <<<- $elem->Detach <<<- $elem->Unmap
A more simple and fast way to move an element from one position in the hierarchy to another is using Reparent.
The dialog is the root of the hierarchy tree. To retrieve the dialog of any element you can simply call GetDialog, but there are other ways to navigate in the hierarchy tree.
You can access any child of the element using the notation "$elem->[n]->[n]....", where n is the index of the child. For example:
XXX-FIXME-NOT-IMLEMENTED-YET my $dialog = IUP::Dialog->new(child=> IUP::Hbox->new(child=>[ IUP::Button->new(TITLE=>"OK"), IUP::Button->new(TITLE=>"Cancel"), ]) ); my $cancel_button = $dialog->->;
The layout size and positioning is automatically updated by Map. Map also updates the dialog layout even if it is already mapped, so using it or using Show, ShowXY or Popup (they all call Map) will also update the dialog layout. The layout size and positioning can be manually updated using Refresh, even if the dialog is not mapped.
After changing containers attributes or element sizes that affect the layout the elements are NOT immediately repositioned. Call Refresh for an element inside the dialog to update the dialog layout. To force a redraw of an element without layout update call Update.
The Layout update is done in two phases. First the layout is computed, this can be done without the dialog being mapped. Second is the native elements update from the computed values.
The Layout computation is done in 3 steps: Natural size computation, update the Current size and update the position.