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 	auto addVariable(Type)(string label, void delegate(Type) setter,
69 		const Type delegate() getter, bool editable = true)
70 	{
71 		Label labelW = new Label(mWindow, label, mLabelFontName, mLabelFontSize);
72 		auto widget = new FormWidget!Type(mWindow);
73 		void delegate() refresh;
74 		(widget, getter){ refresh = {
75 				Type value = getter(), current = widget.value;
76 				if (value != current) widget.value = value;
77 			};
78 		}(widget, getter);
79 
80 		refresh();
81 
82 		widget.callback = setter;
83 		widget.editable = editable;
84 		widget.fontSize = mWidgetFontSize;
85 		Vector2i fs = widget.fixedSize();
86 		widget.fixedSize =Vector2i(fs.x != 0 ? fs.x : mFixedSize.x,
87 			fs.y != 0 ? fs.y : mFixedSize.y);
88 		mRefreshCallbacks.insertBack(refresh);
89 		if (mLayout.rowCount() > 0)
90 			mLayout.appendRow(mVariableSpacing);
91 		mLayout.appendRow(0);
92 		mLayout.setAnchor(labelW, AdvancedGridLayout.Anchor(1, mLayout.rowCount()-1));
93 		mLayout.setAnchor(widget, AdvancedGridLayout.Anchor(3, mLayout.rowCount()-1));
94 		return cast(FormWidget!Type) widget;
95 	}
96 
97 	auto addVariable(Type)(string label, ref Type value, bool editable = true)
98 	{
99 		return addVariable!Type(label, (v) { value = v; },
100 			() { return value; }, editable);
101 	}
102 
103 	/// Add a button with a custom callback
104 	Button addButton(const string label, void delegate() cb)
105 	{
106 		Button button = new Button(mWindow, label);
107 		button.callback = cb;
108 		button.fixedHeight = 25;
109 		if (mLayout.rowCount() > 0)
110 			mLayout.appendRow(mVariableSpacing);
111 		mLayout.appendRow(0);
112 		mLayout.setAnchor(button, AdvancedGridLayout.Anchor(1, mLayout.rowCount()-1, 3, 1));
113 		return button;
114 	}
115 
116 	/// Add an arbitrary (optionally labeled) widget to the layout
117 	void addWidget(const string label, Widget widget)
118 	{
119 		mLayout.appendRow(0);
120 		if (label == "") {
121 			mLayout.setAnchor(widget, AdvancedGridLayout.Anchor(1, mLayout.rowCount()-1, 3, 1));
122 		} else {
123 			Label labelW = new Label(mWindow, label, mLabelFontName, mLabelFontSize);
124 			mLayout.setAnchor(labelW, AdvancedGridLayout.Anchor(1, mLayout.rowCount()-1));
125 			mLayout.setAnchor(widget, AdvancedGridLayout.Anchor(3, mLayout.rowCount()-1));
126 		}
127 	}
128 
129 	/// Cause all widgets to re-synchronize with the underlying variable state
130 	void refresh()
131 	{
132 		foreach (const callback; mRefreshCallbacks) {
133 			callback();
134 		}
135 	}
136 
137 	/// Access the currently active \ref Window instance
138 	Window window() { return mWindow; }
139 
140 	/// Set the active \ref Window instance.
141 	void setWindow(Window window)
142 	{
143 		mWindow = window;
144 		mLayout = cast(AdvancedGridLayout)window.layout;
145 		if (mLayout is null)
146 			throw new Exception(
147 				"Internal error: window has an incompatible layout!");
148 	}
149 
150 	/// Specify a fixed size for newly added widgets.
151 	void setFixedSize(ref const Vector2i fw) { mFixedSize = fw; }
152 
153 	/// The current fixed size being used for newly added widgets.
154 	Vector2i fixedSize() { return mFixedSize; }
155 
156 	/// The font name being used for group headers.
157 	string groupFontName() const { return mGroupFontName; }
158 
159 	/// Sets the font name to be used for group headers.
160 	void setGroupFontName(const string name) { mGroupFontName = name; }
161 
162 	/// The font name being used for labels.
163 	string labelFontName() const { return mLabelFontName; }
164 
165 	/// Sets the font name being used for labels.
166 	void setLabelFontName(const string name) { mLabelFontName = name; }
167 
168 	/// The size of the font being used for group headers.
169 	int groupFontSize() const { return mGroupFontSize; }
170 
171 	/// Sets the size of the font being used for group headers.
172 	void setGroupFontSize(int value) { mGroupFontSize = value; }
173 
174 	/// The size of the font being used for labels.
175 	int labelFontSize() const { return mLabelFontSize; }
176 
177 	/// Sets the size of the font being used for labels.
178 	void setLabelFontSize(int value) { mLabelFontSize = value; }
179 
180 	/// The size of the font being used for non-group / non-label widgets.
181 	int widgetFontSize() const { return mWidgetFontSize; }
182 
183 	/// Sets the size of the font being used for non-group / non-label widgets.
184 	void setWidgetFontSize(int value) { mWidgetFontSize = value; }
185 
186 protected:
187 	/// A reference to the \ref nanogui::Screen this FormHelper is assisting.
188 	Screen mScreen;
189 
190 	/// A reference to the \ref nanogui::Window this FormHelper is controlling.
191 	Window mWindow;
192 
193 	/// A reference to the \ref nanogui::AdvancedGridLayout this FormHelper is using.
194 	AdvancedGridLayout mLayout;
195 
196 	/// The callbacks associated with all widgets this FormHelper is managing.
197 	Array!(void delegate()) mRefreshCallbacks;
198 
199 	/// The group header font name.
200 	string mGroupFontName = "sans-bold";
201 
202 	/// The label font name.
203 	string mLabelFontName = "sans";
204 
205 	/// The fixed size for newly added widgets.
206 	Vector2i mFixedSize = Vector2i(0, 20);
207 
208 	/// The font size for group headers.
209 	int mGroupFontSize = 20;
210 
211 	/// The font size for labels.
212 	int mLabelFontSize = 16;
213 
214 	/// The font size for non-group / non-label widgets.
215 	int mWidgetFontSize = 16;
216 
217 	/// The spacing used **before** new groups.
218 	int mPreGroupSpacing = 15;
219 
220 	/// The spacing used **after** each group.
221 	int mPostGroupSpacing = 5;
222 
223 	/// The spacing between all other widgets.
224 	int mVariableSpacing = 5;
225 
226 public:
227 //	  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
228 };
229 
230 
231 /**
232  * \class FormWidget formhelper.h nanogui/formhelper.h
233  *
234  * \brief A template wrapper class for assisting in the creation of various form widgets.
235  */
236 import std.traits : isBoolean, isFloatingPoint, isSomeString, isIntegral;
237 
238 class FormWidget(T) : CheckBox if(isBoolean!T)
239 {
240 	this(Widget p) { super(p, "", null); fixedWidth = 20; }
241 
242 	alias value = checked;
243 	alias editable = enabled;
244 }
245 
246 class FormWidget(T) : IntBox!T if (isIntegral!T)
247 {
248 	this(Widget p) { super(p); alignment = TextBox.Alignment.Right; }
249 }
250 
251 class FormWidget(T) : FloatBox!T if (isFloatingPoint!T)
252 {
253 	this(Widget p) { super(p); alignment = TextBox.Alignment.Right; }
254 }
255 
256 class FormWidget(T) : TextBox if (isSomeString!T)
257 {
258 	this(Widget p) { super(p); alignment = TextBox.Alignment.Left; }
259 
260 	void callback(void delegate(string) cb) {
261 		super.callback = (string str) { cb(str); return true; };
262 	}
263 }
264 
265 /+
266 template <typename T> class FormWidget<T, typename std::is_enum<T>::type> : public ComboBox {
267 public:
268 	/// Creates a new FormWidget with underlying type ComboBox.
269 	FormWidget(Widget *p) : ComboBox(p) { }
270 
271 	/// Pass-through function for \ref nanogui::ComboBox::selectedIndex.
272 	T value() const { return (T) selectedIndex(); }
273 
274 	/// Pass-through function for \ref nanogui::ComboBox::setSelectedIndex.
275 	void setValue(T value) { setSelectedIndex((int) value); mSelectedIndex = (int) value; }
276 
277 	/// Pass-through function for \ref nanogui::ComboBox::setCallback.
278 	void setCallback(const std::function<void(const T &)> &cb) {
279 		ComboBox::setCallback([cb](int v) { cb((T) v); });
280 	}
281 
282 	/// Pass-through function for \ref nanogui::Widget::setEnabled.
283 	void setEditable(bool e) { setEnabled(e); }
284 
285 public:
286 	EIGEN_MAKE_ALIGNED_OPERATOR_NEW
287 };
288 +/