Layout of widgets in windows


Classes

class  AspectSizable
 "size" property of their objects. More...
class  Place
 A class to position objects in a rectangular area. More...
struct  Rectangle
 Data structure for defining a rectangle. More...

Detailed Description

SmartWin++ Guide

You must set the position and size of each widget after its created, or it won't be visible. The simplest runtime approach is to set the position and size of the widgets during class initialization once.

How can I fix the Widget positions and sizes at linktime ?

You could use the resource editor and set the position and size at compile time. Or you could use the class AspectSizable that has the needed functions to set the position and size of a widget at runtime as most of the samples do. ( All widgets derive from AspectSizable. ) Or you may be able to use the emerging Sally IDE which has the ability to generate SmartWin++ windows.   

Why do Layout at runtime?

In order to respond to a changing runtime situation, you can layout the widgets at runtime. Fixed positions and size become tedious to implement, especially as the program design changes, and they do not adapt well to a changing runtime situation. You will run into cases in which screen space is either wasted or crucial data is hidden.  The program may not adapt well to smaller screen sizes such as are seen in mobile devices.

How does the program know when to redo the layout ?

Typically you create all the widgets in an init function, call a layout function, and then pass a member function to onSized( func ) so that layout is called for each user resize event.  Note that you don't put the onSized() call before you create the widgets, or the resize logic will encounter NULL widgets.

void init()
{
    Creation here

    layout(); // For the first time
    onSized( &HelloWinClass::isResized ); // Make resize event trigger isResized()
}

void isResized( const WindowSizedEventResult & sz )
{
    layout(); // Do layout every time the user resizes the window
}

void layout()
{
    Actual layout code here
}

How to do Layout ?

 The main issue is: How should the position and size of the widget change as the application state changes ?

There are three general approaches:

  1. Have fixed sizes for the widgets, but position the widgets as the window size and proportions change.
  2. Let the contents of the widget dictate the size of the widget. (but this may crowd out the rest of the widgets)
  3. Partition the available space to each widget present.

 

How to divide up a window into different areas

You divide the Rectangle by generating smaller Rectangles using Rectangle generator functions.

Start with the client rectangle of the window, which can be retrieved with getSizeClientArea(). Then use the Rectangle member functions to return other smaller Rectangles. For example,
SmartWin::Rectangle r( getSizeClientArea().size );
Rectangle lowerHalf = r.bottom( 0.5 );
Rectangle lowright_quarter = lowerHalf.right( 0.5 );

Notice that lowright_quarter has 1/4 the area of r, and is its upper left is positioned at the center of r. These also achieve the same result:
Rectangle lowright_quarter = r.bottom( 0.5 ).right( 0.5 );
Rectangle lowright_quarter = r.bottom().right();

Some other Rectangle functions are:
Rectangle left ( double portion= 0.5 );
Rectangle right ( double portion= 0.5 );
Rectangle top ( double portion= 0.5 );
Rectangle bottom ( double portion = 0.5 );
(The new rectangle's size is the old * portion)

Rectangle getLeft ( long x );
Rectangle getRight ( long x );
Rectangle getTop ( long y );
Rectangle getBottom ( long y );
(The new rectangle's size is the x or y value)

Rectangle row ( int row, int rows );
Rectangle col ( int column, int columns );
(Divides the rectangle into rows or columns, and returns the rectangle for the zero based row or col. r2= r1.row( 1, 3) would give you the middle row.)

Rectangle subRect ( double x_fraction, double y_fraction, double width_fraction, double height_fraction );
((Move left and down by x_fraction, y_fraction of the size. Make the new size be width_fraction, height_fraction of the old size. )

Once you get the desired rectangle, you can use the widget method setSize( Rectangle &r)

If its too much trouble to generate the rectangles, and then call setSize(), there are combination methods that does row and column extraction and setSize together:
void setSizeAsCol ( const Rectangle & rect, int rows, int rownum );
void setSizeAsRow ( const Rectangle & rect, int cols, int colnum );

If you keep dividing the rectangles into smaller rectangles until you have the desired position for each widget, you will have the #3 layout scheme.

How to place multiple widgets into an area defined by a Rectangle.

Use the Place class, which sets the .pos member of a Rectangle while absorbing the .size member of the Rectangle. So this is intended for situations in which you know the desired size of a widget, but want to place it with borders and according to the shape of the bounding rectangle. (Layout approach #1)

The Place class has a empty constructor which corresponds to starting with a rectangle based at 0,0 and unlimited size. However you will normally use:

setBoundsBorder( Rectangle & rect, int border_x= 0, int border_y= 0 )

rect specifies the bounding rectangle, and border_x and border_y specify the desired inter-widget spacing. The first widget will therefore have x position of rect.pos.x + border_x.

positionToRight( Rectangle & obj ) is the fundamental routine that sets obj.pos, and internally advances the "current position" according to the obj.size. So obj.size is a input and obj.pos is an output of this function. Afterwards, obj is just the rectangle we want for a setSize( obj ); The method is to place the objects from left to right until there is not enough room in the bounding rectangle on this row, and then start a new row just below the previous row. Note that repeated calls for widgets of different sizes works with setPositionPerPlace(). You can start a new row with:

newRow()

This approach lets the widgets take advange of all the available space and the proportions of the window. For the last widget to be placed in the rectangle, there is a function that allocates all the remaining space available in the bounding rectangle:

sizeForRemainingSpace( Rectangle obj )

Like positionToRight(), it sets obj.pos and takes as input obj.size. You might do a NewRow() just before calling sizeForRemainingSpace().

But suppose you don't know what the best size should be for the widgets. Place has sizeOfCell( rows, cols, Point &Size ) that will set a size Point by dividing up the bounding rectangle into rows and columns, and also takes into account the broders set in setBoundsBorder(). So you can use sizeofCell to generate the size, and then positionToRight() to set the position. This situation is common enough that the AspectSizable implements the:

void setSizeAsGridPerPlace( SmartWin::Place & bound, int rows, int cols );

which takes a Place class and the number of rows/columns of the grid, calculates the desired size, places the rectangle, and then does the setSize() all in one call. There is a similar combined widget function that uses the existing size of the widget for placement:

void setPositionPerPlace( SmartWin::Place &bound );

So you would set the widget size in intialization code, and then in the layout code call setPositionPerPlace(). To clarify, the difference bewteen the two is that setSizeAsGridPerPlace() resizes the Widget into a grid cell first, while setPositionPerPlace() preserves the existing size, and puts the widget into the rectangle in a teletype manner: left to right, and then a new row as needed.

Content driven sizing of widgets.

Are you tired of not having your text fit in the button ? Well, there is a AspectSizable function:

Point getTextSize( tstring & text )

that returns the dimensions of the text given the font used in the widget. You may need some extra padding to account for the widget itself, and you can use the widget function:

void setSizePerTextPerPlace( Place &bound, tstring &text, int extraX= 0, int extraY= 0 )

to combine the getTextSize() and setPositionPerPlace() functions. This is the layout approach #2. A wish list item is a way to scale the font according to the available size.

Internal implementation of layout logic.

Notice that the Rectangle division functions are pure math in BasicTypes.cpp, the Place.h class knows nothing about widgets; it only sets a Rectangle position, and then updates per the Rectangle size it was just given. The Widget knowledgable portion remains in AspectSizeable.h, which is shared by all widgets.


 

Coordinate systems (X,Y relative to what ?)

Back to SmartWin website
SourceForge.net Logo