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 +/