-
Notifications
You must be signed in to change notification settings - Fork 6
Using place for layouts
π
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 |
<
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.
<>
It represents the whole area of the window.
<><>
The two fields represent the left area and right area of the window.
<<>>
for example:
<<><><>>
One outter field
and three nested fields
. The three nested fields represent left area, center area and right area of the window.
There is also an implicit root field. All the fields defined by div are children of this root field (similar to HTML's
tag).<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
.
[1,2,3]
An array with three elements: 1, 2, and 3
[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,...
[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 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"]
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();
If we then replace this line:
plc.div("<abc>");
with:
plc.div("<vertical abc>");
we will get:
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.
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.
arrange
specifies weights for a group of widgets:
place.div("<abc arrange=[50,100]>");
place.field("abc") << btn0 << btn1;
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
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();
}
The grid
lays the buttons out from left to right, top to bottom.
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.
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.
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();
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
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
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();
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();
<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
.
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();
}
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");
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
.
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.
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).
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%>
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:
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>><>><>
#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: