From 713d276b1c0786da3390e4e78dc9de7bd31a8b62 Mon Sep 17 00:00:00 2001 From: Albrecht Schlosser Date: Sun, 7 Aug 2022 15:08:40 +0200 Subject: [PATCH] Fl_Flex: support different margin sizes, improve docs Support different margin sizes on all four edges. Default margin and gap size is now 0 (compatible with Fl_Pack). Doxygen: move the description from the constructor to the class declaration which constitutes a "description". Make some methods virtual and/or 'const'. Clarify demo programs, make them even more "FLTK style". --- FL/Fl_Flex.H | 224 +++++++++++++++++++++++++-- documentation/src/Fl_Flex_simple.png | Bin 0 -> 1952 bytes src/Fl_Flex.cxx | 72 +++------ test/flex_demo.cxx | 94 ++++++----- test/flex_login.cxx | 44 +++--- 5 files changed, 304 insertions(+), 130 deletions(-) create mode 100644 documentation/src/Fl_Flex_simple.png diff --git a/FL/Fl_Flex.H b/FL/Fl_Flex.H index 3d3784492..1224fe828 100644 --- a/FL/Fl_Flex.H +++ b/FL/Fl_Flex.H @@ -20,9 +20,104 @@ #include +/** + Fl_Flex is a container (layout) widget for one row or one column of widgets. + + It provides flexible positioning of its children either in one row or in one column. + + Fl_Flex is designed to be as simple as possible. You can set individual widget + sizes or let Fl_Flex position and size the widgets to fit in the container. + All "flexible" (i.e. non-fixed size) widgets are assigned the same width or + height, respectively. For details see below. + + You can set the margins \b around all children at the inner side the box frame + (if any). Fl_Flex supports setting different margin sizes on top, bottom, left + and right sides. + The default margin size is 0 on all edges of the container. + + You can set the gap size \b between all children. The gap size is always the + same between all of its children. This is similar to the 'spacing' of Fl_Pack. + The default gap size is 0. + + Fl_Flex can either consist of a single row, i.e. \p type(Fl_Flex::HORIZONTAL) + or a single column, i.e. \p type(Fl_Flex::VERTICAL). The default value is + Fl_Flex::VERTICAL for consistency with Fl_Pack but you can use \p type() + to assign a row (Fl_Flex::HORIZONTAL) layout. + + If type() == Fl_Flex::HORIZONTAL widgets are resized horizontally to fit in + the container and their height is the full Fl_Flex height minus border size + and margins. You can set a fixed widget width by using set_size(). + + If type() == Fl_Flex::VERTICAL widgets are resized vertically to fit in + the container and their width is the full Fl_Flex width minus border size + and margins. You can set a fixed widget height by using set_size(). + + To create arbitrary spacing you can use invisible boxes of flexible or + fixed sizes (see example below). + + Alternate constructors let you specify the layout as Fl_Flex::HORIZONTAL or + Fl_Flex::VERTICAL directly. Fl_Flex::ROW is an alias of Fl_Flex::HORIZONTAL + and Fl_Flex::COLUMN is an alias of Fl_Flex::VERTICAL. + + The default box type is FL_NO_BOX as inherited from Fl_Group. You \b may + need to set a box type with a solid background depending on your layout. + + \b Important: You should always make sure that the Fl_Flex container cannot + be resized smaller than its designed minimal size. This can usually be done by + setting a size_range() on the window as shown in the example below. Fl_Flex + does not take care of sensible sizes. If it is resized too small the behavior + is undefined, i.e. widgets may overlap and/or shrink to zero size. + + \b Hint: In many cases Fl_Flex can be used as a drop-in replacement + for Fl_Pack. This is the recommended single row/column container since + FLTK 1.4.0. Its resizing behavior is much more predictable (as expected) + than that of Fl_Pack which "resizes itself to shrink-wrap itself around + all of the children". + + Fl_Flex containers can be nested so you can create flexible layouts with + multiple columns and rows. However, if your UI design is more complex you + may want to use Fl_Grid instead. + At the time of this writing (Aug 7, 2022) Fl_Grid is not yet available + but will be added before FLTK 1.4.0 gets released. + + Example: + \image html Fl_Flex_simple.png + \image latex Fl_Flex_simple.png "Fl_Flex" width=6cm + + Example code: + \code + #include + #include + #include + #include + #include + + int main(int argc, char **argv) { + Fl_Double_Window window(410, 40, "Simple Fl_Flex Demo"); + Fl_Flex flex(5, 5, 400, 30, Fl_Flex::HORIZONTAL); + Fl_Button b1(0, 0, 0, 0, "File"); + Fl_Button b2(0, 0, 0, 0, "Save"); + Fl_Box bx(0, 0, 0, 0); + Fl_Button b3(0, 0, 0, 0, "Exit"); + flex.set_size(bx, 60); // set fix width of invisible box + flex.gap(10); + flex.end(); + window.resizable(flex); + window.end(); + window.size_range(300, 30); + window.show(argc, argv); + return Fl::run(); +} + \endcode + + \since 1.4.0 +*/ class FL_EXPORT Fl_Flex : public Fl_Group { - int margin_; + int margin_left_; + int margin_top_; + int margin_right_; + int margin_bottom_; int gap_; int set_size_size_; int set_size_alloc_; @@ -52,21 +147,65 @@ public: virtual void end(); virtual void resize(int x, int y, int w, int h); + /** + Set the horizontal or vertical size of a child widget. + + \param[in] w widget to be affected + \param[in] size width (Fl_Flex::HORIZONTAL) or height (Fl_Flex::VERTICAL) + + \see set_size(Fl_Widget *w, int size) + */ + void set_size(Fl_Widget &w, int size) { + set_size(&w, size); + } + void set_size(Fl_Widget *w, int size); - int set_size(Fl_Widget *w); + int set_size(Fl_Widget *w) const; protected: void init(int t = VERTICAL); - int alloc_size(int size); + virtual int alloc_size(int size) const; public: - /** Return the margin size of the widget. - \return margin size. + /** Returns the left margin size of the widget. + + This returns the \b left margin of the widget which is not necessarily + the same as all other margins. + + \note This method is useful if you never set different margin sizes. + + \see int margins(int *left, int *top, int *right, int *bottom) + to get all four margin values. + \return size of left margin. */ - int margin() { return margin_; } + int margin() const { return margin_left_; } + + /** Returns all (four) margin sizes of the widget. + + All margin sizes are returned in the given arguments. If any argument + is \p NULL the respective value is not returned. + + \param[in] left returns left margin if not \p NULL + \param[in] top returns top margin if not \p NULL + \param[in] right returns right margin if not \p NULL + \param[in] bottom returns bottom margin if not \p NULL + + \return whether all margins are equal + \retval 1 all margins have the same size + \retval 0 at least one margin has a different size + */ + int margins(int *left, int *top, int *right, int *bottom) const { + if (left) *left = margin_left_; + if (top) *top = margin_top_; + if (right) *right = margin_right_; + if (bottom) *bottom = margin_bottom_; + if (margin_left_ == margin_top_ && margin_top_ == margin_right_ && margin_right_ == margin_bottom_) + return 1; + return 0; + } /** Set the margin and optionally the gap size of the widget. This method can be used to set both the margin and the gap size. @@ -74,27 +213,56 @@ public: If you don't use the second parameter \p g or supply a negative value the gap size is not changed. - The margin is some free space inside the widget border \b around all child - widgets. It has the same size at all four edges of the Fl_Flex widget. + The margin is some free space inside the widget border \b around all child widgets. - The gap size \p g is some free space \b between child widgets. + This method sets the margin to the same size at all four edges of the Fl_Flex widget. + + The gap size \p g is some free space \b between child widgets. Negative values + (the default if this argument is omitted) do not change the gap value. \param[in] m margin size, must be \>= 0 - \param[in] g gap size, must be \>= 0, or will be ignored (if negative) + \param[in] g gap size, ignored (if negative) \see gap(int) */ void margin(int m, int g = -1) { - margin_ = m < 0 ? 0 : m; + if (m < 0) + m = 0; + margin_left_ = margin_top_ = margin_right_ = margin_bottom_ = m; if (g >= 0) gap_ = g; } + /** Set the margin sizes at all four edges of the Fl_Flex widget. + + The margin is the free space inside the widget border \b around all child widgets. + + You must use all four parameters of this method to set the four margins in the + order \p left, \p top, \p right, \p bottom. Negative values are set to 0 (zero). + + To set all margins to equal sizes, use margin(int m) + + This method sets the margin to the same size at all four edges of the widget. + + \param[in] left,top,right,bottom margin sizes, must be \>= 0 + + \see margin(int, int) + */ + + void margin(int left, int top, int right, int bottom) { + margin_left_ = left < 0 ? 0 : left; + margin_top_ = top < 0 ? 0 : top; + margin_right_ = right < 0 ? 0 : right; + margin_bottom_ = bottom < 0 ? 0 : bottom; + } + /** Return the gap size of the widget. \return gap size between all child widgets. */ - int gap() { return gap_; } + int gap() const { + return gap_; + } /** Set the gap size of the widget. @@ -134,6 +302,32 @@ public: redraw(); } + /** + Gets the number of extra pixels of blank space that are added + between the children. + + This method is the same as 'int gap()' and is defined to enable + using Fl_Flex as a drop-in replacement of Fl_Pack. + + \see int gap() + */ + int spacing() const { + return gap_; + } + + /** + Sets the number of extra pixels of blank space that are added + between the children. + + This method is the same as 'gap(int)' and is defined to enable + using Fl_Flex as a drop-in replacement of Fl_Pack. + + \see void gap(int) + */ + void spacing(int i) { + gap(i); + } + #if (1) // Additional methods for backwards compatibility with "original" Fl_Flex widget @@ -141,14 +335,18 @@ public: /** Deprecated. \deprecated Please use set_size(Fl_Widget *) instead. + + \see int set_size(Fl_Widget *) */ - bool isSetSize(Fl_Widget *w) { + bool isSetSize(Fl_Widget *w) const { return (bool)set_size(w); } /** Set the horizontal or vertical size of a child widget. \deprecated Please use set_size(Fl_Widget *, int) instead. + + \see set_size(Fl_Widget *, int) */ void setSize(Fl_Widget *w, int size) { set_size(w, size); diff --git a/documentation/src/Fl_Flex_simple.png b/documentation/src/Fl_Flex_simple.png new file mode 100644 index 0000000000000000000000000000000000000000..8547a945715c6743063add7c71850d944ebe2add GIT binary patch literal 1952 zcmV;R2VeM!P)Px+T1iAfRCt{2-Dyx0XBY?YXOnORBpTEpidB+gg5rRv$e|*NAQ+HCrAoc3)L6Xg zffX&pYHh#^#{;iXtk(Mi$D^odRMaX^ud0aSfl^VhC>}sa(hnd4#;}x1HreMl`M_jY z=ACDr_n&unUp7RdTmc9nr2kj|AaQ5udKd^H)L2zPmiJ1n+i@U-(0_LLhEn3rV(5Ap z2qERSPo1+7pu`RMdk?xXyj$s4p!KdfqlkSVF* z!6s>tI!#h}D?szfW|^`H$3+`hRa7r*lE%;+XG;8t_zbYBs9xA4jq!;%!(C{f0ag{& z3!9`daH9r7$UtD6A%qMB#pCRC%GVa2(Wfiw{)`aP2}(~xio{u2dQ?jRm%*=owd%Ma zLjl0D?Hw`5#hTFd`1FOU=)%_NKE5K3$l9Wt$UZ1+OwySGyc&Z(;iGxSWpwJh;EuMt zt2L)&@qrN*ePhm1C)z0VEDct-A2uu-YE>Csgo%|M3&6-ZGRaxT3dLDNyt@`3Ala($k3V@V^X9J$z4Mm^&EAnm^DR3ByA6+1aaW%9xb)-H zQNJtWcud)}`PA*SwBJUzPMbGsL3SAcFk1VE_j+~c@VPftzgK)L^@Or_SU?K`i%ee( zUTEx!@Zy!@R$MGmzsXn`B`XSA6F0=BMqZfoTa}yO$8IN)-+7_BSCILgNcZOe6pAy2 zJj%W0`xAJd`1@M2zU0WJO@)JH8%BF~wQbwc!GC^iWcv#{4nC#%R5SI7S~`jQ`$%tr ztzSTJ^p8spXa4>ES;H**u(=Td9zwhJ?S%bC$mV%IJ#hTG0sx4XVzF20$$cl@005|q z({`R^`HTCvAh5{vP;=h#xvU;jk1GHGi01thei^OYJ~?XXtZ~Vu<9=Q^m`nL<*c82X zMzGtq`*hYTbF*&0e3%<-zOBZaOu>NXDzKqYJ~ut4O~CTK1Lo#lIh&Q8bydE3e$wLP z*+-IRy0St2`ugUj(!}Featcezm1;E*oZvzg006=D3iXwKzf1lyaFFG*llxAY`tI;( z2Fh$KGA-1;c19R+?W#h7uymcbF(SQB(thEDltu0w%7+=H;;ixg{@q2DssmH zoP?%-ojIZHd=vqgw*A6}p?FnNs#ZR^e7(>yND}5?!!c$M%KO)I-qyIjX3;;;&n#pA zp_?hYvRZ|Tdzk?i8I@(PkCHz0*^(S9ybv>S_k)icfyrQivVuDMG^n&XBah$&Mo#QJ zLN^qp>%{XyqBbLq0y>A`3*x3XCi%c8p*H3b8{8?1J zQG;dGUTAAhyvdFqmsB_`apzZV&3ny0;}$<{b&ow$_(nAkHMMTnvN$U}JI_I2ZfwSB zZmR3f(96{sfSKd0qg!KLpRA7v>Dg&;aKy6Ir`CL)9-Q2NR;JEg-rRe;W9N+Y4Dj@j zNK@1Umu}fSxdRiRSzz?4@v8M+o+AE0@!TgtGyG~_3NdjV?%Vp#tp|Mn5SRBKaad$p zsK{D8#;cpSZ`CVM9CulvE|JGXuPY5(kyO=s1xNyw}JThIX@Q6>!^J)vm_rpHKb_{6G`N5eg|m9R<3tJ5Tv+h{e!82}{8 zq)k3NeMNraVO2?@XO>mMCLyU#lW2=Ggb>mr_)ks 1 ? cc - 1 : 0; int hori = horizontal(); - int space = (hori ? w - dw : h - dh) - 2 * margin_; + int space = hori ? (w - dw - margin_left_ - margin_right_) + : (h - dh - margin_top_ - margin_bottom_); // set x and y (start) position, calculate widget sizes - int xp = x + dx + margin_; - int yp = y + dy + margin_; - int hh = h - dh - margin_ * 2; // if horizontal: constant height of widgets - int vw = w - dw - margin_ * 2; // if vertical: constant width of widgets + int xp = x + dx + margin_left_; + int yp = y + dy + margin_top_; + int hh = h - dh - margin_top_ - margin_bottom_; // if horizontal: constant height of widgets + int vw = w - dw - margin_left_ - margin_right_; // if vertical: constant width of widgets int fw = cc; // number of flexible widgets @@ -307,7 +281,7 @@ void Fl_Flex::set_size(Fl_Widget *w, int size) { \retval 1 the widget has a fixed size \retval 0 the widget resizes dynamically */ -int Fl_Flex::set_size(Fl_Widget *w) { +int Fl_Flex::set_size(Fl_Widget *w) const { for (int i = 0; i < set_size_size_; i++) { if (w == set_size_[i]) { return 1; @@ -330,6 +304,6 @@ int Fl_Flex::set_size(Fl_Widget *w) { \param[in] size current size \return int new size (to be allocated) */ -int Fl_Flex::alloc_size(int size) { +int Fl_Flex::alloc_size(int size) const { return size + 8; } diff --git a/test/flex_demo.cxx b/test/flex_demo.cxx index fb2b73cef..67726910a 100644 --- a/test/flex_demo.cxx +++ b/test/flex_demo.cxx @@ -36,7 +36,7 @@ void debug_group(Fl_Group *g) { #endif } // debug_group -Fl_Button *createButton(const char *caption) { +Fl_Button *create_button(const char *caption) { Fl_Button *rtn = new Fl_Button(0, 0, 120, 30, caption); rtn->color(fl_rgb_color(225, 225, 225)); return rtn; @@ -74,21 +74,21 @@ void toggle_cb(Fl_Widget *w, void *v) { debug_group(flex); } -Fl_Flex *createRow() { +Fl_Flex *create_row() { Fl_Flex *row = new Fl_Flex(Fl_Flex::ROW); { - Fl_Button *toggle = createButton("hide OK button"); + Fl_Button *toggle = create_button("hide OK button"); toggle->tooltip("hide() or show() OK button"); Fl_Box *box2 = new Fl_Box(0, 0, 120, 10, "Box2"); - Fl_Button * okay = createButton("OK"); + Fl_Button * okay = create_button("OK"); new Fl_Input(0, 0, 120, 10, ""); toggle->callback(toggle_cb, okay); Fl_Flex *col2 = new Fl_Flex(Fl_Flex::COLUMN); { - createButton("Top2"); - createButton("Bottom2"); + create_button("Top2"); + create_button("Bottom2"); col2->end(); col2->margin(0, 5); col2->box(FL_FLAT_BOX); @@ -110,59 +110,53 @@ Fl_Flex *createRow() { int main(int argc, char **argv) { Fl_Window *window = new Fl_Double_Window(100, 100, "Simple GUI Example"); - { - Fl_Flex *col = new Fl_Flex(5, 5, 90, 90, Fl_Flex::COLUMN); - { - Fl_Flex *row = new Fl_Flex(Fl_Flex::ROW); - row->color(FL_YELLOW); - row->box(FL_FLAT_BOX); - { - createButton("Cancel"); - new Fl_Box(0, 0, 120, 10, "Box1"); - createButton("OK"); - new Fl_Input(0, 0, 120, 10, ""); + Fl_Flex *col = new Fl_Flex(5, 5, 90, 90, Fl_Flex::COLUMN); + Fl_Flex *row1 = new Fl_Flex(Fl_Flex::ROW); + row1->color(FL_YELLOW); + row1->box(FL_FLAT_BOX); + create_button("Cancel"); + new Fl_Box(0, 0, 120, 10, "Box1"); + create_button("OK"); + new Fl_Input(0, 0, 120, 10, ""); - Fl_Flex *col1 = new Fl_Flex(Fl_Flex::COLUMN); - { - createButton("Top1"); - createButton("Bottom1"); - col1->end(); - col1->box(FL_FLAT_BOX); - col1->color(fl_rgb_color(255, 128, 128)); - col1->margin(5, 5); - } + Fl_Flex *col1 = new Fl_Flex(Fl_Flex::COLUMN); + create_button("Top1"); + create_button("Bottom1"); + col1->box(FL_FLAT_BOX); + col1->color(fl_rgb_color(255, 128, 128)); + col1->margin(5, 5); + col1->end(); + row1->end(); - row->end(); - } - col->set_size(createRow(), 90); - createButton("Something1"); - row = new Fl_Flex(Fl_Flex::ROW); - { - Fl_Button *cancel = createButton("Cancel"); - Fl_Button *ok = createButton("OK"); - new Fl_Input(0, 0, 120, 10, ""); + col->set_size(create_row(), 90); // sets height of created (anonymous) row #2 - row->set_size(cancel, 100); - row->set_size(ok, 100); - row->end(); - } - createButton("Something2"); + create_button("Something1"); // "row" #3 - col->set_size(row, 30); - col->margin(0, 6); - col->end(); - } - window->resizable(col); - window->color(fl_rgb_color(160, 180, 240)); - window->box(FL_FLAT_BOX); - window->end(); - } + Fl_Flex *row4 = new Fl_Flex(Fl_Flex::ROW); + Fl_Button *cancel = create_button("Cancel"); + Fl_Button *ok = create_button("OK"); + new Fl_Input(0, 0, 120, 10, ""); + row4->set_size(cancel, 100); + row4->set_size(ok, 100); + row4->end(); + + create_button("Something2"); // "row" #5 + + col->set_size(row4, 30); + col->margin(6, 10, 6, 10); + col->gap(6); + col->end(); + + window->resizable(col); + window->color(fl_rgb_color(160, 180, 240)); + window->box(FL_FLAT_BOX); + window->end(); window->size_range(550, 330); window->resize(0, 0, 640, 480); window->show(argc, argv); int ret = Fl::run(); - delete window; + delete window; // not necessary but useful to test for memory leaks return ret; } diff --git a/test/flex_login.cxx b/test/flex_login.cxx index 3923bcde3..e8098cbce 100644 --- a/test/flex_login.cxx +++ b/test/flex_login.cxx @@ -22,13 +22,15 @@ #include #include -Fl_Button *createButton(const char *caption) { +Fl_Button *create_button(const char *caption) { Fl_Button *rtn = new Fl_Button(0, 0, 100, 25, caption); rtn->color(fl_rgb_color(225, 225, 225)); return rtn; } -void buttonsPanel(Fl_Flex *parent) { +// create widgets inside a column, i.e. parent is type(COLUMN) + +void buttons_panel(Fl_Flex *parent) { new Fl_Box(0, 0, 0, 0, ""); Fl_Box *w = new Fl_Box(0, 0, 0, 0, "Welcome to Flex Login"); @@ -57,8 +59,8 @@ void buttonsPanel(Fl_Flex *parent) { Fl_Flex *brow = new Fl_Flex(Fl_Flex::ROW); { new Fl_Box(0, 0, 0, 0, ""); - Fl_Button *reg = createButton("Register"); - Fl_Button *login = createButton("Login"); + Fl_Button *reg = create_button("Register"); + Fl_Button *login = create_button("Login"); brow->set_size(reg, 80); brow->set_size(login, 80); @@ -77,7 +79,9 @@ void buttonsPanel(Fl_Flex *parent) { parent->set_size(b, 30); } -void middlePanel(Fl_Flex *parent) { +// create widgets inside a row, i.e. parent is type(ROW) + +void middle_panel(Fl_Flex *parent) { new Fl_Box(0, 0, 0, 0, ""); Fl_Box *box = new Fl_Box(0, 0, 0, 0, "Image"); @@ -86,7 +90,7 @@ void middlePanel(Fl_Flex *parent) { Fl_Box *spacer = new Fl_Box(0, 0, 0, 0, ""); Fl_Flex *bp = new Fl_Flex(Fl_Flex::COLUMN); - buttonsPanel(bp); + buttons_panel(bp); bp->end(); new Fl_Box(0, 0, 0, 0, ""); @@ -96,14 +100,19 @@ void middlePanel(Fl_Flex *parent) { parent->set_size(bp, 300); } +// The main panel consists of three "rows" inside a column, i.e. parent is +// type(COLUMN). The middle panel has a fixed size (200) such that the two +// boxes take the remaining space and middle_panel has all widgets. + void mainPanel(Fl_Flex *parent) { - new Fl_Box(0, 0, 0, 0, ""); + + new Fl_Box(0, 0, 0, 0, ""); // flexible separator Fl_Flex *mp = new Fl_Flex(Fl_Flex::ROW); - middlePanel(mp); + middle_panel(mp); mp->end(); - new Fl_Box(0, 0, 0, 0, ""); + new Fl_Box(0, 0, 0, 0, ""); // flexible separator parent->set_size(mp, 200); } @@ -111,21 +120,20 @@ void mainPanel(Fl_Flex *parent) { int main(int argc, char **argv) { Fl_Window *window = new Fl_Double_Window(100, 100, "Simple GUI Example"); - { - Fl_Flex *col = new Fl_Flex(5, 5, 90, 90, Fl_Flex::COLUMN); - mainPanel(col); - col->end(); - window->resizable(col); - window->color(fl_rgb_color(250, 250, 250)); - window->end(); - } + Fl_Flex *col = new Fl_Flex(5, 5, 90, 90, Fl_Flex::COLUMN); + mainPanel(col); + col->end(); + + window->resizable(col); + window->color(fl_rgb_color(250, 250, 250)); + window->end(); window->resize(0, 0, 640, 480); window->size_range(550, 250); window->show(argc, argv); int ret = Fl::run(); - delete window; + delete window; // not necessary but useful to test for memory leaks return ret; }