├── LICENSE ├── QLayoutsDSL └── q_layouts_dsl.hpp └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yurii Nakonechnyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QLayoutsDSL/q_layouts_dsl.hpp: -------------------------------------------------------------------------------- 1 | #ifndef Q_LAYOUTS_DSL_HPP 2 | #define Q_LAYOUTS_DSL_HPP 3 | 4 | // ============================================================================= 5 | // ████████╗██╗ ██╗██████╗ ██╗ ███████╗ ███████╗ ██████╗ ██████╗ ███████╗ █████╗ ██████╗██╗ ██╗ 6 | // ╚══██╔══╝██║ ██║██╔══██╗██║ ██╔════╝ ██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝██║ ██║ 7 | // ██║ ██║ ██║██████╔╝██║ █████╗ █████╗ ██║ ██║██████╔╝█████╗ ███████║██║ ███████║ 8 | // ██║ ██║ ██║██╔═══╝ ██║ ██╔══╝ ██╔══╝ ██║ ██║██╔══██╗██╔══╝ ██╔══██║██║ ██╔══██║ 9 | // ██║ ╚██████╔╝██║ ███████╗███████╗ ██║ ╚██████╔╝██║ ██║███████╗██║ ██║╚██████╗██║ ██║ 10 | // ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ 11 | 12 | #include 13 | 14 | // via: https://stackoverflow.com/a/16387374 15 | 16 | // Alternate solution: 17 | // - https://stackoverflow.com/a/26912970 18 | // + Port of std::index_sequence for c++11: https://stackoverflow.com/a/32223343 19 | // 20 | // In case of post c++11 version - use it: 21 | // - https://www.fluentcpp.com/2019/03/08/stl-algorithms-on-tuples/ 22 | 23 | namespace dsl { 24 | 25 | namespace meta { 26 | 27 | namespace detail 28 | { 29 | template 30 | struct seq { }; 31 | 32 | template 33 | struct gen_seq : gen_seq { }; 34 | 35 | template 36 | struct gen_seq<0, Is...> : seq { }; 37 | } 38 | 39 | namespace detail 40 | { 41 | template 42 | inline void for_each(T& tuple, F&& func, seq) 43 | { 44 | auto l = { (func(std::get(tuple)), 0)... }; 45 | (void)l; // Suppress compiler warning 46 | } 47 | 48 | template 49 | inline void for_each(const T& tuple, F&& func, seq) 50 | { 51 | auto l = { (func(std::get(tuple)), 0)... }; 52 | (void)l; // Suppress compiler warning 53 | } 54 | } 55 | 56 | template 57 | inline void for_each_in_tuple(std::tuple& tuple, F&& func) 58 | { 59 | constexpr auto COUNT = sizeof...(Ts); 60 | detail::for_each(tuple, func, detail::gen_seq()); 61 | } 62 | 63 | template 64 | inline void for_each_in_tuple(const std::tuple& tuple, F&& func) 65 | { 66 | constexpr auto COUNT = sizeof...(Ts); 67 | detail::for_each(tuple, func, detail::gen_seq()); 68 | } 69 | 70 | } // namespace meta 71 | 72 | } // namespace dsl 73 | 74 | // ============================================================================= 75 | 76 | // ██╗ █████╗ ██╗ ██╗ ██████╗ ██╗ ██╗████████╗ ██████╗ ███████╗██╗ 77 | // ██║ ██╔══██╗╚██╗ ██╔╝██╔═══██╗██║ ██║╚══██╔══╝ ██╔══██╗██╔════╝██║ 78 | // ██║ ███████║ ╚████╔╝ ██║ ██║██║ ██║ ██║ ██║ ██║███████╗██║ 79 | // ██║ ██╔══██║ ╚██╔╝ ██║ ██║██║ ██║ ██║ ██║ ██║╚════██║██║ 80 | // ███████╗██║ ██║ ██║ ╚██████╔╝╚██████╔╝ ██║ ██████╔╝███████║███████╗ 81 | // ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ 82 | 83 | #include 84 | 85 | namespace dsl { 86 | 87 | /// Widget info 88 | struct w_info { 89 | // Default values, which passes into: QBoxLayout::addWidget() 90 | // https://doc.qt.io/Qt-5/qboxlayout.html#addWidget 91 | 92 | QWidget* widget = nullptr; 93 | int stretch = 0; 94 | Qt::Alignment alignment = Qt::Alignment(); 95 | 96 | explicit w_info(QWidget* w, int s = 0, Qt::Alignment a = Qt::Alignment()) 97 | : widget(w), stretch(s), alignment(a) 98 | {} 99 | }; 100 | 101 | /// Layout info 102 | struct l_info { 103 | // Default values, which passes into: QBoxLayout::addLayout() 104 | // https://doc.qt.io/qt-5/qboxlayout.html#addLayout 105 | 106 | QLayout *layout = nullptr; 107 | int stretch = 0; 108 | 109 | explicit l_info(QLayout* l, int s = 0) 110 | : layout(l), stretch(s) 111 | {} 112 | }; 113 | 114 | struct stretch { 115 | int _stretch = 0; 116 | explicit stretch(int s = 0) 117 | : _stretch(s) 118 | {} 119 | }; 120 | 121 | struct spacing { 122 | int _spacing = 0; 123 | explicit spacing(int s) 124 | : _spacing(s) 125 | {} 126 | }; 127 | 128 | // ============================================================================= 129 | 130 | template 131 | struct WidgetWrapper 132 | { 133 | // Captured 134 | WidgetT* _widget = nullptr; 135 | 136 | // Default 137 | int _stretch = 0; 138 | Qt::Alignment _alignment = Qt::Alignment(); 139 | 140 | public: 141 | 142 | explicit WidgetWrapper(WidgetT* w) 143 | : _widget(w) 144 | {} 145 | 146 | WidgetWrapper& stretch(int s) { 147 | _stretch = s; 148 | return *this; 149 | } 150 | 151 | WidgetWrapper& alignment(Qt::Alignment a) { 152 | _alignment = a; 153 | return *this; 154 | } 155 | 156 | }; 157 | 158 | // ============================================================================= 159 | 160 | namespace detail { 161 | 162 | template 163 | struct layout_fill 164 | { 165 | static_assert (std::is_base_of::value == true, 166 | "BaseLayoutT must be derived from QLayout"); 167 | 168 | // Captured 169 | BaseLayoutT* lay = nullptr; 170 | 171 | // Make capture 172 | explicit layout_fill(BaseLayoutT* lay_) 173 | : lay(lay_) 174 | {} 175 | 176 | // ------------------------------------------------------------------------- 177 | 178 | // Call .addWidget(), if item is QWidget 179 | void operator () (QWidget* widget) 180 | { 181 | lay->addWidget(widget); 182 | } 183 | 184 | // Call .addLayout(), if item is QLayout 185 | void operator () (QLayout* layout) 186 | { 187 | lay->addLayout(layout); 188 | } 189 | 190 | // ------------------------------------------------------------------------- 191 | 192 | // Call .addWidget(widget*, int stretch, Qt::Alignment align), if item is struct with info about it 193 | void operator () (const w_info& i) 194 | { 195 | lay->addWidget(i.widget, i.stretch, i.alignment); 196 | } 197 | 198 | // Call .addLayout(layout*, int stretch), if item is struct with info about it 199 | void operator () (const l_info& i) 200 | { 201 | lay->addLayout(i.layout, i.stretch); 202 | } 203 | 204 | // ------------------------------------------------------------------------- 205 | 206 | // Call .addSpacing(int spacing), if item is struct with info about spacing 207 | void operator () (const spacing& s) 208 | { 209 | lay->addSpacing(s._spacing); 210 | } 211 | 212 | // Call .addStretch(int stretch), if item is struct with info about stretch 213 | void operator () (const stretch& s) 214 | { 215 | lay->addStretch(s._stretch); 216 | } 217 | 218 | // ========================================================================= 219 | 220 | template 221 | void operator () (const WidgetWrapper& wrapper) 222 | { 223 | lay->addWidget(wrapper._widget, wrapper._stretch, wrapper._alignment); 224 | } 225 | }; 226 | 227 | } // namespace detail 228 | 229 | 230 | // TODO: C++17 231 | // - Use std::apply instead of for_each_in_tuple 232 | // - Use std::visit instead of detail::layout_fill 233 | // 234 | // - Or instead of using `tuple`, simply use fold expressions 235 | // - https://en.cppreference.com/w/cpp/language/fold 236 | 237 | 238 | // Univercal reference (Items&& ... items) everywhere, because in function can be 239 | // passed pointers (QLayout*, QWidget*) and non-pointers (info{widget, stretch, alignment}) 240 | 241 | /// Fill existing @a layout by @a items (layouts and widgets) 242 | template 243 | LayoutT* add(LayoutT* layout, Items&& ... items) 244 | { 245 | // const std::tuple t {items ...}; // <-- not works, produces compiling errors 246 | const auto t = std::forward_as_tuple(items ...); // Pack variadic argumets into tuple for iteration over it 247 | 248 | detail::layout_fill filler(layout); // Capture layout 249 | meta::for_each_in_tuple(t, filler); // Call detail::layout_fill::() operator for each item in tuple 250 | 251 | return layout; 252 | } 253 | 254 | /// Create new layout and fill it by @a items (layouts and widgets) 255 | template 256 | LayoutT* make(Items&& ... items) 257 | { 258 | LayoutT* layout = new LayoutT; 259 | return add(layout, items ...); 260 | } 261 | 262 | // ============================================================================= 263 | 264 | template 265 | struct LayoutWrapper { 266 | 267 | static_assert (std::is_base_of::value == true, 268 | "LayoutT must be derived from QLayout"); 269 | 270 | LayoutT* captured = nullptr; 271 | 272 | explicit LayoutWrapper(LayoutT* lay) 273 | : captured(lay) 274 | {} 275 | 276 | LayoutWrapper& spacing(int spacing) { 277 | captured->setSpacing(spacing); 278 | return *this; 279 | } 280 | 281 | LayoutWrapper& margin(int margin) { 282 | captured->setMargin(margin); 283 | return *this; 284 | } 285 | 286 | LayoutWrapper& contentsMargins(int left, int top, int right, int bottom) { 287 | captured->setContentsMargins(left, top, right, bottom); 288 | return *this; 289 | } 290 | 291 | // Implicit conversion to layout 292 | operator LayoutT* () { 293 | return captured; 294 | } 295 | 296 | }; 297 | 298 | template 299 | LayoutWrapper make_ex(Items&& ... items) 300 | { 301 | return LayoutWrapper( make(items...) ); 302 | } 303 | 304 | // ----------------------------------------------------------------------------- 305 | 306 | template 307 | WidgetWrapper add(WidgetT* widget) { 308 | return WidgetWrapper(widget); 309 | } 310 | 311 | } // namespace dsl 312 | 313 | #endif // Q_LAYOUTS_DSL_HPP 314 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qt Layouts DSL 2 | 3 |

4 | 5 | 6 | 7 |

8 | 9 | C++ [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) for making layouts (or filling existing) by items (other layouts or widgets) 10 | 11 | Single-header-only. 12 | 13 | Language version: **c++11** 14 | 15 | Imaginary pseudo-code (inspired by [Flutter](https://flutter.dev/)): 16 | 17 | ```txt 18 | { 19 | { 20 | Button1, 21 | { 22 | Button2, 23 | { Button3, Label4 } 24 | } 25 | }, 26 | 27 | { Button5, LineEdit6 }, 28 | 29 | Button7 30 | } 31 | ``` 32 | 33 | # Usage 34 | 35 | 1. In `*.pro` file add next line: `INCLUDEPATH += $${PWD}/path/to/QLayoutsDSL/` 36 | 2. In code use: ```#include ``` 37 | 38 | # Examples of usage 39 | 40 | ## Simple 41 | 42 | ```c++ 43 | auto* layout = make( 44 | new QPushButton("Button1", this), 45 | new QPushButton("Button2", this), 46 | new QLabel ("Label3", this) 47 | ); 48 | ``` 49 | 50 | ## Advanced 51 | 52 | ```c++ 53 | auto* layout = make( 54 | make( 55 | Button1, 56 | make( 57 | Button2, 58 | make( Button3, Label4 ) 59 | ) 60 | ), 61 | 62 | l_info( make( Button5, LineEdit6 ), 3), 63 | stretch(2), 64 | Button7, 65 | spacing(25) 66 | ); 67 | ``` 68 | 69 | ## Extended 70 | 71 | ```c++ 72 | QVBoxLayout* vbox = new QVBoxLayout; 73 | add(vbox, 74 | w_info(new QPushButton("Stretch: 3", this), 3), 75 | w_info(new QPushButton("Stretch: 2", this), 2, Qt::AlignRight), 76 | w_info(new QPushButton("Stretch: 1", this), 1) 77 | ); 78 | ``` 79 | 80 | ## Hacky 81 | 82 | ### Before (initialization **before** filling) 83 | 84 | ```c++ 85 | inline void clearSpacing(QLayout* layout) { 86 | // Remove spaces between widgets and outer margin 87 | layout->setSpacing(0); layout->setMargin(0); 88 | } 89 | 90 | class MyWidget 91 | { 92 | QVBoxLayout* vbox1 = nullptr; 93 | QHBoxLayout* hbox1 = nullptr; 94 | QVBoxLayout* vbox2 = nullptr; 95 | QHBoxLayout* hbox2 = nullptr; 96 | 97 | MyWidget() 98 | { 99 | vbox1 = new QVBoxLayout; clearSpacing(vbox1); 100 | hbox1 = new QHBoxLayout; clearSpacing(hbox1); 101 | vbox2 = new QVBoxLayout; clearSpacing(vbox2); 102 | hbox2 = new QHBoxLayout; clearSpacing(hbox2); 103 | 104 | using namespace dsl; 105 | 106 | add(vbox1, 107 | new QPushButton("Button 1", this), 108 | add(hbox1, 109 | new QPushButton("Button 2", this), 110 | 111 | add(vbox2, 112 | new QPushButton("Button 3", this), 113 | 114 | add(hbox2, 115 | new QPushButton("Button 4", this) 116 | ) 117 | ) 118 | ) 119 | ); 120 | 121 | this->setLayout(vbox1); 122 | } 123 | } 124 | ``` 125 | 126 | ### After (initialization **during** filling) 127 | 128 | ```c++ 129 | template 130 | class ZeroLayout: public LayoutT 131 | { 132 | public: 133 | template 134 | explicit ZeroLayout(Args&& ... args) 135 | : LayoutT(args ...) 136 | { 137 | this->setMargin(0); 138 | this->setSpacing(0); 139 | } 140 | }; 141 | 142 | class MyWidget 143 | { 144 | QVBoxLayout* vbox1 = nullptr; 145 | QHBoxLayout* hbox1 = nullptr; 146 | QVBoxLayout* vbox2 = nullptr; 147 | QHBoxLayout* hbox2 = nullptr; 148 | 149 | MyWidget() 150 | { 151 | using namespace dsl; 152 | 153 | vbox1 = make< ZeroLayout > 154 | ( 155 | new QPushButton("Button 1", this), 156 | 157 | hbox1 = make< ZeroLayout > 158 | ( 159 | new QPushButton("Button 2", this), 160 | 161 | vbox2 = make< ZeroLayout > 162 | ( 163 | new QPushButton("Button 3", this), 164 | 165 | hbox2 = make< ZeroLayout > 166 | ( 167 | new QPushButton("Button 4", this) 168 | ) 169 | ) 170 | ) 171 | ); 172 | 173 | this->setLayout(vbox1); 174 | } 175 | } 176 | ``` 177 | 178 | ## Additional usage 179 | 180 | ```c++ 181 | QVBoxLayout* vbox1 = nullptr; 182 | QHBoxLayout* hbox1 = nullptr; 183 | QVBoxLayout* vbox2 = nullptr; 184 | QHBoxLayout* hbox2 = nullptr; 185 | 186 | 187 | QPushButton* btn = new QPushButton("new", this); 188 | btn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 189 | 190 | { 191 | using namespace dsl; 192 | 193 | vbox1 = make_ex< QVBoxLayout > 194 | ( 195 | new QPushButton("Button 1", this), 196 | 197 | hbox1 = make< QHBoxLayout > 198 | ( 199 | new QPushButton("Button 2", this), 200 | 201 | vbox2 = make< QVBoxLayout > 202 | ( 203 | new QPushButton("Button 3", this), 204 | 205 | hbox2 = make< QHBoxLayout > 206 | ( 207 | new QPushButton("Button 4", this) 208 | ) 209 | ) 210 | ), 211 | 212 | add(btn).stretch(2).alignment(Qt::AlignLeft) // <- Extra feature 213 | 214 | ).margin(2).spacing(5).contentsMargins(2, 20, 2, 100); // <- Extra feature 215 | } 216 | ``` 217 | --------------------------------------------------------------------------------