Skip to content

Using place for layouts

Adeola Ibigbemi edited this page Apr 15, 2024 · 17 revisions

πŸ“š
Nana's widgets form and panel can contain other widgets. The relative positions and size (the layouts) of the child widgets can be explicitly specified using coordinates. A more convenient way to define the layout is by using place. Generally, an object of class place binds to that parent widget and automatically positions and resizes the widgets which are the children of that widget. The desired format or layout is set with div(const char* div_text); which causes the default or root field (the surface of the attached widget) to be divided into other fields in the specified manner using the position rules described below. Then, the child widgets are inserted into the desired field for layout adjustments using a map-like notation. If the field name specified is not (or will not be) present in the div-text, an invisible field is created and all of the widgets corresponding to that field will be hidden. An entire field can be temporarily retired from the view using field_display(const char* name, bool display);. A widget can be permanently retired from a field using erase(window handle);. A field can also be made invisible with field_visible(const char* name, bool visibility);; the difference here is that an invisible field will still occupy the space, while a hidden field will not. Finally, collocate(); collocates (arranges) the fields and widgets through the custom rules set in the div() call. In most cases, place collocates automatically, when the bound window resizes; but, when you modify the div's rules, collocate() should be invoked manually. Otherwise, the widgets will not be readjusted until the place-bound window is resized.

Note that the div-text can eventually become complex with many nested angle brackets. It is easy to make a mistake. Some malformed div-texts will be accepted without complaint, producing not what you expected to be displayed. Some errors may cause exceptions to be thrown, so you should surround code that uses place with a try{...div(); collocate();}catch(std::exception){} block (specially during debugging), otherwise the application will abruptly crash.

In development: 🚧

  • void modify (const char *field_name, const char *div_text) method to modify a specified field.
  • template<typename Panel , typename... Args> void dock (const char *dockname, Args &&...args) - Add a panel factory

The field is a basic concept for place. It represents an area in the window. There are four different types of fields. Each type describes how widgets are positioned:

Field type Description
Horizontal Widgets are positioned horizontally, like the icons in the Windows taskbar. This is the default.
Vertical Widgets are positioned vertically, like items in a drop-down menu.
Grid Widgets are positioned in a tabular grid, like the cells in Microsoft Excel (but nana::listbox is a better approximation to an Excel sheet).
Splitter A splitter bar is used for let the user to adjust the size of fields on both sides.
Dock Children fields are dockable
Switchable Only shows one of the children fields

Div syntax

Symbols and keywords

< and >, |, %, [ and ], arrange, bottom, collapse, dock, fit, gap, grid, invisible, margin min= or max=, left, repeated, right, switchable, top, undisplayed, variable, vertical or vert, width or height (or weight),

The div-text is used to divide a widget into various fields in order to position and resize its child widgets.

Root field. There is an implicit root field. All the fields defined by Div-Text are children of that root field.

Define a field

        <>

It represents the whole area of the window.

        <><>

The two fields represent the left area and right area of the window.

Nest fields within each other

        <<>>

for example:

        <<><><>>

One outter field and three nested fields. The three nested fields represent left area, center area and right area of the window.

Root field

There is also an implicit root field. All the fields defined by div are children of this root field (similar to HTML's

tag).

Splitter bar

<a>|<b> OR <a>|30<b> OR <a>|30%<b>   <horizontal <a>|<b>>   <vertical <a>|<b>>

Sets a splitter bar between field a and b. It can be horizontal or vertical.

When a number is specified after|, it stands for an initial weight of the right field. 30% indicates that the percent of total pixels = (the weight of a + the weight of b) * 30%. In such cases, the attribute's weight will be ignored if specified for a or b.

Arrays

[1,2,3]

An array with three elements: 1, 2, and 3

Repeated elements

[1,2,3,repeated]

The keyword repeated indicates the array is countless (looping indefinitely), e.g.: 1,2,3,1,2,3,1,2,3,...

Variable element in an array

[1,2,variable,3]

The keyword variable is an unspecified value. It's interpreted differently by different attributes of field (arrange, gap, margin). Refer to the Attributes of a field section for more details.

Specify attributes for a field

Name

Specify an identifier for the name of a field

    < id_you_specified >  

The field is named id_you_specified, and we can refer to it by using:

    place_obj.field("id_you_specified")

or:

    place_obj["id_you_specified"]

Vertical layout

Specify vertical or vert to create a field for which all of its child fields are laid out vertically. If this is not specified, the field's child fields will be laid out horizontally by default. For example:

    place plc(fm);      // fm is an instance of ::nana::form

    plc.div("<abc>");
    plc.field("abc") << btn0 << btn1 << btn2 << btn3 ; // These buttons are children of fm.
    plc.collocate();

Place Horizontal

If we then replace this line:

        plc.div("<abc>");

with:

        plc.div("<vertical abc>");

we will get:

Place Vertical

Attribute width or height (or alternatively: weight)

Weight is used to specify the width or height of a field. It depends on its owner field's layout type. If no match vertical-height/horizontal-width occurred between a division and a child division an exception of type place::error will be throw.

Specify the weight in pixels:

        <abc><width=200 def>
        <abc><weight=200 def>

(Here, if the width of the form is 1000px, the field abc is 800px wide and def is 200px wide.) but

        <extern <abc><height=200 def>>

Will trow a place::error showing unmatch vertical-height/horizontal-width between division 'extern' and children division 'def'

or in percentages:

        <abc><width=60% def><ghi>

Here, if the width of form is 1000px, the field abc is 200px wide, def is 600px wide, and ghi is 200px wide.

Minimum or maximum

min and max specify the minimum or maximum weight for a field; however, if a field is specified with a weight and either min or max at same time, the weight attribute will be ignored.

Arrangement

arrange specifies weights for a group of widgets:

    place.div("<abc arrange=[50,100]>");
    place.field("abc") << btn0 << btn1;

Place Vertical

If the number of widgets in the field is larger than the number of elements in arrange, like so:

    place.div("<abc arrange=[50,100]>");
    place.field("abc") << btn0 << btn1 << btn2 << btn3;

the extra widgets (btn2 and btn3) will be auto-sized by place.

Specify variable in the array to indicate that a corresponding widget's weight is unspecified and the widgets (btn1 and btn4 here) will be resized by place instead.

    place.div("<abc arrange=[30,variable,60,repeated]>");
    place.field("abc") << btn0 << btn1 << btn2 << btn3 << btn4 << btn5 << btn6;

After enlarging, the width of widget 1 and 4 is changed by the place, because their corresponding weight in arrange is variable.

grid [X, Y]

grid specifies a field that lays its child widgets out as a grid. For example, to create a 3x2 grid, use:

        <grid [3, 2]>

The field is divided into 3 X 2 grid. For 3x3:

    place plc(fm);
    plc.div("< abc grid=[3,3]> ");
    std::vector<std::unique_ptr<button>> btns;

    for(auto i = 0; i < 9; ++i) {
       btns.emplace_back(std::make_unique<button>(fm.handle(), "Button"));)
       plc["abc"] << *btns.back();
    }

Place Grid

The grid lays the buttons out from left to right, top to bottom.

Collapses

collape merges blocks (like in Excel with the "merge" feature):

    <grid=[3,2] collapse(0,1,3,1)>

collapse( column index, row index, column count to merge, row count to merge). The above collapse merges 3 columns and 1 row beginning from column 0 and row 1.

Place Grid

More than one collapse can be utilized, but overlapping collapses will be ignored:

    <grid=[3,2] collapse(0,1,3,1) collapse(1,1,2,2)> 

The second collapse here is ignored, because it overlaps with the first.

Gaps

gap creates a gap in pixels between widgets in any field (similar to HTML's "margin" attribute, but only for fields). The value of gap is an array:

        <grid [3, 2] gap=5>

When a field is a grid, gap can be specified by an array; but, it always uses the first element. All other elements will be ignored.

When the first element of the array for gap is a variable, the variable will be interpreted as zero:

    <grid=[3,3] gap=[variable]>

The above is equal to:

    <grid=[3,3] gap=0>

Gap can also work in conjunction with arrange:

    <arrange=80 gap=5>

gap=5 is equal to gap=[5,repeated]

Gaps will only appear between widgets. gap interprets an array differently than any other attributes. Refer to margin and arrange for more details.

place.div("<fld gap=[5,10,20]>");
for (int i = 0; i < 5; ++i)
{
	buttons.emplace_back(std::make_unique<button>(fm.handle(), std::to_string(i)));
	place["fld"] << *buttons.back();
}
place.collocate();

Gap

As you see, there are 3 gaps.

The gap can be assigned with an integral number, the gap interprets the integral number as an repeated array.

<gap=5> is interpreted as <gap=[5,repeated]>

If the element of gap's array is variable, the variable here will be interpreted as zero.

Margin

margin specifies the empty space around the sides of the place field, like with HTML's "margin" attribute. It can have anywhere from 1 to 4 values:

    <margin=[10,20,30,40]>

top margin = 10
right margin = 20
bottom margin = 30
left margin = 40

    <margin=[10,20,30]>

top margin = 10
right margin = 20
bottom margin = 30

    <margin=[10,20]>

top and bottom margin = 10
left and right margin = 20

    <margin=[10]>

top margin = 10 (notice the square brackets [])

    <margin=20>

top, right, bottom and left margin = 20 (no square brackets)

If variable is specified in the array, it stands for zero.

    <margin=[variable]>

top margin = 0

    <margin=[variable,repeated]>

top, right, bottom, and left margin = 0

Fit-Content Layout

fit

The fit defines a field that layouts a measurable widget to fit its content.

btna.caption("This is very long long long title");
btnb.caption("Short title");
place.div("<><fit x gap=5><>");
place["x"]<<btna<<btnb;
place.collocate();

fit

vfit

The vfit layouts a measurable widget to fit its content, and the height of the content is measured with a specified fixed width. At present (Nana 1.7) only label supports vfit.

std::string content = "Nana C++ Library takes aim at easy-to-use and portable library, it provides a GUI framework and threads for easy programming with modern C++ methods.";

label.caption(content);
place.div("<><vfit=200 x><>");
place["x"]<<label;
place.collocate();

vfit

Splitter bar

<a>|<b>

Sets a splitter bar between field a and b. When a number is specified behand |, it stands for an initial weight of right field.

<a>|30<b>

Field b's weight is 30px.

<a>|30%<b>

If the number is a percentage, it indicates the percentage of sum of left and right field. The weight of field b is (weight of a + weight of b) * 30%. Note: the initial weight attribute will be ignored if the weight is specified for right field, for example.

<a>|30%<b weight=20>

The 30% is ignored because of b's weight=20.

switchable

When a field is specified with switchable, it only shows one child field. If display another child field using field_display method, the shown child field will be hidden. It performs like a tab control.

int main()
{
    using namespace nana;

    form fm;

    button switch_btn{ fm };

    listbox list{ fm };
    textbox text{ fm };
    label labl{ fm };
    labl.caption("label");
    fm.div("<btn><switchable <list><text><label>>");
    fm["btn"] << switch_btn;
    fm["list"] << list;
    fm["text"] << text;
    fm["label"] << labl;
    fm.collocate();


    //one of the panel list, text and label is shown when every time the switch_btn get clicked.
    switch_btn.events().click([&] {
        static std::vector<std::string> panels{"list", "text", "label"};
        static int i = 0;
        ++i;
        if (i >= panels.size())
            i = 0;

        //Displays one of the field, other 2 fields will be hidden automatically.
        fm.get_place().field_display(panels[i].c_str(), true);
        fm.collocate();
    });

    fm.show();
    exec();
}

Advanced Fields

dock

When a field is specified with dock, its child fields are dockable. These child fields called dockable field. A widget that attaches to a dockable field can be docked inside the dock field or floated on the desktop. The dockable field can't have a child field.

Dockpane is a library maintained internal window which attaches to the dockable field, it has a title bar and a close button, it is the parent of user widgets which attaches to dockable field.

//Create a dock field that has one dockable field.
place.div("<dock<dockableA>>");

//Create a dockpane factory that creates a button.
place.dock<button>("dockableA", "f", std::string{"Button"});

//Create a dockpane
place.dock_create("f");

Dock

The line

place.dock<button>("dockableA", "f", std::string("Button"));

Adds a factory that creates a button for the dockable field "dockableA", The name of the factory is "f", the 3rd parameter and after the 3rd parameters, are passed to the constructor of class button. The button is constructed by following function call

button{DockPaneHandle, std::forward<Args>(third_args)...};

If a factory doesn't have a name, the factory will be called immediately. Because of no name, the factory also can't be called by using place::dock_create().

The line

place.dock_create("f");

calls the specified factory "f" which creates a button. See above screenshot. If the "f" factory get called many times, a tabbar will be created for switching the multiple buttons that attaches to the same dockable field.

Dock tabswitch

top, right, bottom and left

These 4 attributes specify the position of a dockable field to the next silbing dockable field. Therefore the position attribute of last dockable field is always ignored.

weight

Specifies width or height of a dockable field. But the weight behaves differently with other type fields if the weight is assigned with a percentage. The percentage weight for dockable field is a percentage of sum of current dockable field and its right field(s).

dock-weight

Above screenshot illustrates the weight of dockable field when the dock field is defined as

<dock><A weight=25%><B weight=25%><C weight=50%>

An illustration

Let's create a user interface for login validation. The program GUI will look like this:

First of all, we should divide the form into fields. For the desired result, it may be divided like this: Divided into fields

The div text of the red (outer) rectangle should be:

    <><weight=80% vertical child_fields_of_red_rectangle><>

The root field is horizontal by default, so these 3 fields are laid out horizontally. The red rectangle in the middle takes 80% of the space, since we specified its weight of 80%. As you can see, the children of the red rectangle are laid out vertically, because we specified for it to be vertical.

The div text of the orange (middle) rectangle should be:

    <><weight=70% vertical child_fields_of_orange_rectangle><>

The orange rectangle is laid out in the middle, consuming 70% of the space, and it is also vertical.

The div-text of both the green (innermost) and blue rectangles:

   <vertical gap=10 textboxes arrange=[25,25]>  <weight=25 gap=10 buttons>

The widgets in the green rectangle are laid out vertically, and the widgets in the blue rectangle are laid out horizontally, so only the green rectangle needed this specified, since the default is horizontal.

The User and Password boxes are single-line text boxes, and we specified a fixed height for them. The blue rectangle lays out the buttons horizontally; in other words, the height of the blue rectangle is the same as buttons' heights, so we specified its weight to be 25 pixels.

We will refer to these 2 fields (textboxes and buttons) later on in order to insert widgets into them, so a name is given to each.

Here are the combined div-texts:

    <><weight=80% vertical <><weight=70% vertical <vertical gap=10 textboxes arrange=[25,25]>  <weight=25 gap=10 buttons>><>><>

Let's start programming: :octocat:

    #include <nana/gui/wvl.hpp>
    #include <nana/gui/place.hpp>
    #include <nana/gui/widgets/button.hpp>
    #include <nana/gui/widgets/textbox.hpp>

    int main()
    {
        using namespace nana;

        // Define widgets
        form fm;
        textbox usr  {fm},   
                pswd {fm};
        button  login {fm, "Login"}, 
                cancel{fm, "Cancel"};

        usr .tip_string("User:"    ).multi_lines(false);
        pswd.tip_string("Password:").multi_lines(false).mask('*');

        // Define a place for the form.
        place plc{fm};

        // Divide the form into fields
        plc.div("<><weight=80% vertical<><weight=70% vertical <vertical gap=10 textboxes arrange=[25,25]>  <weight=25 gap=10 buttons> ><>><>");

        //Insert widgets

        //The field textboxes is vertical. It automatically adjusts the widgets' top and height attributes. 
        plc.field("textboxes") << usr << pswd;

        plc["buttons"] << login << cancel;

        //Finially, the widgets should be collocated.
        //Do not miss this line! Otherwise, the widgets will not be collocated
        //until the form is resized.
        plc.collocate();

        fm.show();
        exec();
    }

And the final result:

Clone this wiki locally