1 module nanogui.formhelper; 2 /* 3 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>. 4 The widget drawing code is based on the NanoVG demo application 5 by Mikko Mononen. 6 7 All rights reserved. Use of this source code is governed by a 8 BSD-style license that can be found in the LICENSE.txt file. 9 */ 10 /** 11 * \file nanogui/formhelper.h 12 * 13 * \brief Helper class to construct forms for editing a set of variables of 14 * various types. 15 */ 16 import std.container : Array; 17 18 import nanogui.common; 19 import nanogui.screen : Screen; 20 import nanogui.label : Label; 21 import nanogui.button : Button; 22 import nanogui.checkbox : CheckBox; 23 import nanogui.textbox : TextBox, FloatBox, IntBox; 24 import nanogui.combobox : ComboBox; 25 import nanogui.layout : AdvancedGridLayout; 26 import nanogui.window : Window; 27 import nanogui.widget : Widget; 28 29 /** 30 * \class FormHelper formhelper.h nanogui/formhelper.h 31 * 32 * \brief Convenience class to create simple AntTweakBar-style layouts that 33 * expose variables of various types using NanoGUI widgets 34 * 35 */ 36 class FormHelper { 37 public: 38 /// Create a helper class to construct NanoGUI widgets on the given screen 39 this(Screen screen) { mScreen = screen; } 40 41 /// Add a new top-level window 42 Window addWindow(const Vector2i pos, 43 const string title = "Untitled") 44 { 45 assert(mScreen); 46 mWindow = new Window(mScreen, title); 47 mLayout = new AdvancedGridLayout([10, 0, 10, 0], []); 48 mLayout.margin(10); 49 mLayout.setColStretch(2, 1); 50 mWindow.position = pos; 51 mWindow.layout = mLayout; 52 mWindow.visible = true; 53 return mWindow; 54 } 55 56 /// Add a new group that may contain several sub-widgets 57 Label addGroup(string caption) 58 { 59 Label label = new Label(mWindow, caption, mGroupFontName, mGroupFontSize); 60 if (mLayout.rowCount() > 0) 61 mLayout.appendRow(mPreGroupSpacing); /* Spacing */ 62 mLayout.appendRow(0); 63 mLayout.setAnchor(label, AdvancedGridLayout.Anchor(0, mLayout.rowCount()-1, 4, 1)); 64 mLayout.appendRow(mPostGroupSpacing); 65 return label; 66 } 67 68 template addVariable(Type) 69 { 70 auto addVariable(string label, void delegate(Type) setter, 71 const Type delegate() getter, bool editable = true) 72 { 73 Label labelW = new Label(mWindow, label, mLabelFontName, mLabelFontSize); 74 auto widget = new FormWidget!Type(mWindow); 75 void delegate() refresh; 76 (widget, getter){ refresh = { 77 Type value = getter(), current = widget.value; 78 if (value != current) widget.value = value; 79 }; 80 }(widget, getter); 81 82 refresh(); 83 84 widget.callback = setter; 85 widget.editable = editable; 86 widget.fontSize = mWidgetFontSize; 87 Vector2i fs = widget.fixedSize(); 88 widget.fixedSize =Vector2i(fs.x != 0 ? fs.x : mFixedSize.x, 89 fs.y != 0 ? fs.y : mFixedSize.y); 90 mRefreshCallbacks.insertBack(refresh); 91 if (mLayout.rowCount() > 0) 92 mLayout.appendRow(mVariableSpacing); 93 mLayout.appendRow(0); 94 mLayout.setAnchor(labelW, AdvancedGridLayout.Anchor(1, mLayout.rowCount()-1)); 95 mLayout.setAnchor(widget, AdvancedGridLayout.Anchor(3, mLayout.rowCount()-1)); 96 return cast(FormWidget!Type) widget; 97 } 98 99 auto addVariable(string label, ref Type value, bool editable = true) 100 { 101 return addVariable!Type(label, (v) { value = v; }, 102 () { return value; }, editable); 103 } 104 } 105 106 /// Add a button with a custom callback 107 Button addButton(const string label, void delegate() cb) 108 { 109 Button button = new Button(mWindow, label); 110 button.callback = cb; 111 button.fixedHeight = 25; 112 if (mLayout.rowCount() > 0) 113 mLayout.appendRow(mVariableSpacing); 114 mLayout.appendRow(0); 115 mLayout.setAnchor(button, AdvancedGridLayout.Anchor(1, mLayout.rowCount()-1, 3, 1)); 116 return button; 117 } 118 119 /// Add an arbitrary (optionally labeled) widget to the layout 120 void addWidget(const string label, Widget widget) 121 { 122 mLayout.appendRow(0); 123 if (label == "") { 124 mLayout.setAnchor(widget, AdvancedGridLayout.Anchor(1, mLayout.rowCount()-1, 3, 1)); 125 } else { 126 Label labelW = new Label(mWindow, label, mLabelFontName, mLabelFontSize); 127 mLayout.setAnchor(labelW, AdvancedGridLayout.Anchor(1, mLayout.rowCount()-1)); 128 mLayout.setAnchor(widget, AdvancedGridLayout.Anchor(3, mLayout.rowCount()-1)); 129 } 130 } 131 132 /// Cause all widgets to re-synchronize with the underlying variable state 133 void refresh() 134 { 135 foreach (const callback; mRefreshCallbacks) { 136 callback(); 137 } 138 } 139 140 /// Access the currently active \ref Window instance 141 Window window() { return mWindow; } 142 143 /// Set the active \ref Window instance. 144 void setWindow(Window window) 145 { 146 mWindow = window; 147 mLayout = cast(AdvancedGridLayout)window.layout; 148 if (mLayout is null) 149 throw new Exception( 150 "Internal error: window has an incompatible layout!"); 151 } 152 153 /// Specify a fixed size for newly added widgets. 154 void setFixedSize(ref const Vector2i fw) { mFixedSize = fw; } 155 156 /// The current fixed size being used for newly added widgets. 157 Vector2i fixedSize() { return mFixedSize; } 158 159 /// The font name being used for group headers. 160 string groupFontName() const { return mGroupFontName; } 161 162 /// Sets the font name to be used for group headers. 163 void setGroupFontName(const string name) { mGroupFontName = name; } 164 165 /// The font name being used for labels. 166 string labelFontName() const { return mLabelFontName; } 167 168 /// Sets the font name being used for labels. 169 void setLabelFontName(const string name) { mLabelFontName = name; } 170 171 /// The size of the font being used for group headers. 172 int groupFontSize() const { return mGroupFontSize; } 173 174 /// Sets the size of the font being used for group headers. 175 void setGroupFontSize(int value) { mGroupFontSize = value; } 176 177 /// The size of the font being used for labels. 178 int labelFontSize() const { return mLabelFontSize; } 179 180 /// Sets the size of the font being used for labels. 181 void setLabelFontSize(int value) { mLabelFontSize = value; } 182 183 /// The size of the font being used for non-group / non-label widgets. 184 int widgetFontSize() const { return mWidgetFontSize; } 185 186 /// Sets the size of the font being used for non-group / non-label widgets. 187 void setWidgetFontSize(int value) { mWidgetFontSize = value; } 188 189 protected: 190 /// A reference to the \ref nanogui::Screen this FormHelper is assisting. 191 Screen mScreen; 192 193 /// A reference to the \ref nanogui::Window this FormHelper is controlling. 194 Window mWindow; 195 196 /// A reference to the \ref nanogui::AdvancedGridLayout this FormHelper is using. 197 AdvancedGridLayout mLayout; 198 199 /// The callbacks associated with all widgets this FormHelper is managing. 200 Array!(void delegate()) mRefreshCallbacks; 201 202 /// The group header font name. 203 string mGroupFontName = "sans-bold"; 204 205 /// The label font name. 206 string mLabelFontName = "sans"; 207 208 /// The fixed size for newly added widgets. 209 Vector2i mFixedSize = Vector2i(0, 20); 210 211 /// The font size for group headers. 212 int mGroupFontSize = 20; 213 214 /// The font size for labels. 215 int mLabelFontSize = 16; 216 217 /// The font size for non-group / non-label widgets. 218 int mWidgetFontSize = 16; 219 220 /// The spacing used **before** new groups. 221 int mPreGroupSpacing = 15; 222 223 /// The spacing used **after** each group. 224 int mPostGroupSpacing = 5; 225 226 /// The spacing between all other widgets. 227 int mVariableSpacing = 5; 228 229 public: 230 // EIGEN_MAKE_ALIGNED_OPERATOR_NEW 231 }; 232 233 234 /** 235 * \class FormWidget formhelper.h nanogui/formhelper.h 236 * 237 * \brief A template wrapper class for assisting in the creation of various form widgets. 238 */ 239 import std.traits : isBoolean, isFloatingPoint, isSomeString, isIntegral; 240 241 template FormWidget(T) 242 { 243 static if(isBoolean!T) 244 { 245 class FormWidget : CheckBox 246 { 247 this(Widget p) { super(p, "", null); fixedWidth = 20; } 248 249 alias value = checked; 250 alias editable = enabled; 251 } 252 } 253 else static if (isIntegral!T) 254 { 255 class FormWidget : IntBox!T 256 { 257 this(Widget p) { super(p); alignment = TextBox.Alignment.Right; } 258 } 259 } 260 else static if (isFloatingPoint!T) 261 { 262 class FormWidget : FloatBox!T 263 { 264 this(Widget p) { super(p); alignment = TextBox.Alignment.Right; } 265 } 266 } 267 else static if (isSomeString!T) 268 { 269 class FormWidget : TextBox 270 { 271 this(Widget p) { super(p); alignment = TextBox.Alignment.Left; } 272 273 void callback(void delegate(string) cb) { 274 super.callback = (string str) { cb(str); return true; }; 275 } 276 } 277 } 278 } 279 280 /+ 281 template <typename T> class FormWidget<T, typename std::is_enum<T>::type> : public ComboBox { 282 public: 283 /// Creates a new FormWidget with underlying type ComboBox. 284 FormWidget(Widget *p) : ComboBox(p) { } 285 286 /// Pass-through function for \ref nanogui::ComboBox::selectedIndex. 287 T value() const { return (T) selectedIndex(); } 288 289 /// Pass-through function for \ref nanogui::ComboBox::setSelectedIndex. 290 void setValue(T value) { setSelectedIndex((int) value); mSelectedIndex = (int) value; } 291 292 /// Pass-through function for \ref nanogui::ComboBox::setCallback. 293 void setCallback(const std::function<void(const T &)> &cb) { 294 ComboBox::setCallback([cb](int v) { cb((T) v); }); 295 } 296 297 /// Pass-through function for \ref nanogui::Widget::setEnabled. 298 void setEditable(bool e) { setEnabled(e); } 299 300 public: 301 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 302 }; 303 +/