1 /*
2 	nanogui.widget -- Base class of all widgets
3 
4 	NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch> and
5 	ported to D by Alexandr Druzhinin <drug2004@bk.ru>
6 	The widget drawing code is based on the NanoVG demo application
7 	by Mikko Mononen.
8 
9 	All rights reserved. Use of this source code is governed by a
10 	BSD-style license that can be found in the LICENSE.txt file.
11 */
12 ///
13 module nanogui.widget;
14 
15 import std.container.array;
16 
17 import nanogui.theme;
18 import nanogui.layout;
19 import nanogui.common : Cursor, Vector2i, Vector2f, MouseButton, KeyAction;
20 import nanogui.screen : Screen;
21 import nanogui.window : Window;
22 public import nanogui.common;
23 
24 /**
25  * Base class of all widgets.
26  *
27  * Widget is the base class of all widgets in nanogui. It can
28  * also be used as an panel to arrange an arbitrary number of child
29  * widgets using a layout generator (see `nanogui.layout.Layout`).
30  */
31 class Widget
32 {
33 public:
34 	/// Construct a new widget with the given parent widget
35 	this(Widget parent)
36 	{
37 		mVisible = true;
38 		mEnabled = true;
39 		mTooltip = ""; 
40 		mFontSize = -1;
41 		mIconExtraScale = 1.0f; 
42 		mCursor = Cursor.Arrow;
43 		if (parent)
44 			parent.addChild(this);
45 	}
46 
47 	/// Return the parent widget
48 	final Widget parent() { return mParent; }
49 	/// Return the parent widget
50 	auto parent() const { return mParent; }
51 	/// Set the parent widget
52 	final void parent(Widget parent) { mParent = parent; }
53 
54 	/// Return the used `nanogui.layout.Layout` generator
55 	final Layout layout() { return mLayout; }
56 	/// Return the used `nanogui.layout.Layout` generator
57 	auto layout() const { return mLayout; }
58 	/// Set the used `nanogui.layout.Layout` generator
59 	final void layout(Layout layout) { mLayout = layout; }
60 
61 	/// Return the `nanogui.theme.Theme` used to draw this widget
62 	final const(Theme) theme() const { return mTheme; }
63 	/// Set the `nanogui.theme.Theme` used to draw this widget
64 	void theme(Theme theme)
65 	{
66 		if (mTheme is theme)
67 			return;
68 		mTheme = theme;
69 		foreach(child; mChildren)
70 			child.theme = theme;
71 	}
72 
73 	/// Return the position relative to the parent widget
74 	final Vector2i position() const { return mPos; }
75 	/// Set the position relative to the parent widget
76 	final void position(Vector2i pos) { mPos = pos; }
77 
78 	/// Return the absolute position on screen
79 	final Vector2i absolutePosition() const
80 	{
81 		return mParent ?
82 			(parent.absolutePosition + mPos) : mPos;
83 	}
84 
85 	/// Return the size of the widget
86 	final Vector2i size() const { return mSize; }
87 	/// set the size of the widget
88 	final void size(Vector2i size) { mSize = size; }
89 
90 	/// Return the width of the widget
91 	final int width() const { return mSize.x; }
92 	/// Set the width of the widget
93 	final void width(int width) { mSize.x = width; }
94 
95 	/// Return the height of the widget
96 	final int height() const { return mSize.y; }
97 	/// Set the height of the widget
98 	final void height(int height) { mSize.y = height; }
99 
100 	/**
101 	 * Set the fixed size of this widget
102 	 *
103 	 * If nonzero, components of the fixed size attribute override any values
104 	 * computed by a layout generator associated with this widget. Note that
105 	 * just setting the fixed size alone is not enough to actually change its
106 	 * size; this is done with a call to `nanogui.widget.Widget.size` or a call to `nanogui.widget.Widget.performLayout`
107 	 * in the parent widget.
108 	 */
109 	final void fixedSize(Vector2i fixedSize) { mFixedSize = fixedSize; }
110 
111 	/// Return the fixed size
112 	final Vector2i fixedSize() const { return mFixedSize; }
113 
114 	/// Return the fixed width (see `fixedSize`)
115 	final int fixedWidth() const { return mFixedSize.x; }
116 	/// Return the fixed height (see `fixedSize`)
117 	final int fixedHeight() const { return mFixedSize.y; }
118 	/// Set the fixed width (see `fixedSize`)
119 	final void fixedWidth(int width) { mFixedSize.x = width; }
120 	/// Set the fixed height (see `fixedSize`)
121 	final void fixedHeight(int height) { mFixedSize.y = height; }
122 
123 	/// Return whether or not the widget is currently visible (assuming all parents are visible)
124 	final bool visible() const { return mVisible; }
125 	/// Set whether or not the widget is currently visible (assuming all parents are visible)
126 	final void visible(bool visible) { mVisible = visible; }
127 
128 	/// Check if this widget is currently visible, taking parent widgets into account
129 	final bool visibleRecursive() const {
130 		import std.typecons : Rebindable;
131 		bool visible = true;
132 		Rebindable!(const Widget) widget = this;
133 		while (widget) {
134 			visible &= widget.visible;
135 			widget = widget.parent;
136 		}
137 		return visible;
138 	}
139 
140 	/// Return the number of child widgets
141 	final int childCount() const
142 	{
143 		import std.conv : castFrom;
144 		return castFrom!size_t.to!int(mChildren.length);
145 	}
146 
147 	/// Return the list of child widgets of the current widget
148 	auto children() { return mChildren; }
149 	/// ditto
150 	auto children() const { return mChildren; }
151 
152 	/**
153 	* Add a child widget to the current widget at
154 	* the specified index.
155 	*
156 	* This function almost never needs to be called by hand,
157 	* since the constructor of `Widget` automatically
158 	* adds the current widget to its parent
159 	*/
160 	void addChild(int index, Widget widget)
161 	{
162 		assert(index <= childCount);
163 		mChildren.insertBefore(mChildren[index..$], widget);
164 		widget.parent = this;
165 		widget.theme = mTheme;
166 	}
167 
168 	/// Convenience function which appends a widget at the end
169 	void addChild(Widget widget)
170 	{
171 		addChild(childCount(), widget);
172 	}
173 
174 	/// Remove a child widget by index
175 	void removeChild(int index)
176 	{
177 		import std.range : takeOne;
178 		mChildren.linearRemove(mChildren[index..$].takeOne);
179 	}
180 
181 	/// Remove a child widget by value
182 	void removeChild(Widget widget)
183 	{
184 		import std.algorithm : find;
185 		import std.range : takeOne;
186 		mChildren.linearRemove(mChildren[].find(widget).takeOne);
187 	}
188 
189 	/// Retrieves the child at the specific position
190 	auto childAt(int index) const { return mChildren[index]; }
191 
192 	/// Retrieves the child at the specific position
193 	auto childAt(int index) { return mChildren[index]; }
194 
195 	/// Returns the index of a specific child or -1 if not found
196 	int childIndex(Widget widget) const
197 	{
198 		import std.algorithm : countUntil;
199 		return cast(int) countUntil!(a=>a is widget)(mChildren[]);
200 	}
201 
202 	/// Variadic shorthand notation to construct and add a child widget
203 	auto add(W, Args...)(Args args)
204 	{
205 		return new WidgetClass(this, args);
206 	}
207 
208 	/// Walk up the hierarchy and return the parent window
209 	final Window window()
210 	{
211 		Widget widget = this;
212 		while (true) {
213 			if (!widget)
214 				throw new Exception(
215 					"Widget:internal error (could not find parent window)");
216 			Window window = cast(Window)(widget);
217 			if (window)
218 				return window;
219 			widget = widget.parent;
220 		}
221 	}
222 
223 	/// Walk up the hierarchy and return the parent screen
224 	final Screen screen()
225 	{
226 		auto widget = this;
227 		while (true) {
228 			if (!widget)
229 				throw new Exception(
230 					"Widget:internal error (could not find parent screen)");
231 			auto screen = cast(Screen) widget;
232 			if (screen)
233 				return screen;
234 			widget = widget.parent;
235 		}
236 	}
237 
238 	/// Associate this widget with an ID value (optional)
239 	void setId(string id) { mId = id; }
240 	/// Return the ID value associated with this widget, if any
241 	auto id() const { return mId; }
242 
243 	/// Return whether or not this widget is currently enabled
244 	final bool enabled() const { return mEnabled; }
245 	/// Set whether or not this widget is currently enabled
246 	final void enabled(bool enabled) { mEnabled = enabled; }
247 
248 	/// Return whether or not this widget is currently focused
249 	bool focused() const { return mFocused; }
250 	/// Set whether or not this widget is currently focused
251 	void focused(bool focused) { mFocused = focused; }
252 	/// Request the focus to be moved to this widget
253 	void requestFocus()
254 	{
255 		import nanogui.screen : Screen;
256 		Widget widget = this;
257 		while (widget.parent())
258 			widget = widget.parent();
259 		if (auto screen = cast(Screen) widget)
260 			screen.updateFocus(this);
261 		else
262 			throw new Exception("Invariant violation");
263 	}
264 
265 	string tooltip() const { return mTooltip; }
266 	void tooltip(string tooltip) { mTooltip = tooltip; }
267 
268 	/// Return current font size. If not set the default of the current theme will be returned
269 	final int fontSize() const
270 	{
271 		return (mFontSize < 0 && mTheme) ? mTheme.mStandardFontSize : mFontSize;
272 	}
273 	/// Set the font size of this widget
274 	final void fontSize(int fontSize) { mFontSize = fontSize; }
275 	/// Return whether the font size is explicitly specified for this widget
276 	final bool hasFontSize() const { return mFontSize > 0; }
277 
278 	/**
279 	* The amount of extra scaling applied to *icon* fonts.
280 	* See `nanogui.Widget.mIconExtraScale`.
281 	*/
282 	float iconExtraScale() const { return mIconExtraScale; }
283 
284 	/**
285 	* Sets the amount of extra scaling applied to *icon* fonts.
286 	* See `nanogui.Widget.mIconExtraScale`.
287 	*/
288 	void iconExtraScale(float scale) { mIconExtraScale = scale; }
289 
290 	/// Return a pointer to the cursor of the widget
291 	Cursor cursor() const { return mCursor; }
292 	/// Set the cursor of the widget
293 	void cursor(Cursor value) { mCursor = value; }
294 
295 	/// Check if the widget contains a certain position
296 	final bool contains(Vector2i p) const {
297 		import std.algorithm : all;
298 		// the widget contains a position if it more than
299 		// the widget position and less than widget position
300 		// + widget size
301 		auto d = (p-mPos);
302 		return d[].all!"a>=0" && (d-mSize)[].all!"a<=0";
303 	}
304 
305 	/// ditto
306 	final bool contains(Vector2f p) const {
307 		import std.algorithm : all;
308 		// the widget contains a position if it more than
309 		// the widget position and less than widget position
310 		// + widget size
311 		auto d = (p-mPos);
312 		return d[].all!"a>=0" && (d-mSize)[].all!"a<=0";
313 	}
314 
315 	/// Determine the widget located at the given position value (recursive)
316 	Widget findWidget(Vector2i p)
317 	{
318 		foreach_reverse(child; mChildren)
319 		{
320 			if (child.visible() && child.contains(p - mPos))
321 				return child.findWidget(p - mPos);
322 		}
323 		return contains(p) ? this : null;
324 	}
325 
326 	/// Handle a mouse button event (default implementation: propagate to children)
327 	bool mouseButtonEvent(Vector2i p, MouseButton button, bool down, int modifiers)
328 	{
329 		foreach_reverse(ch; mChildren)
330 		{
331 			Widget child = ch;
332 			if (child.visible && child.contains(p - mPos) &&
333 				child.mouseButtonEvent(p - mPos, button, down, modifiers))
334 				return true;
335 		}
336 		if (button == MouseButton.Left && down && !mFocused)
337 			requestFocus();
338 		return false;
339 	}
340 
341 	/// Handle a mouse motion event (default implementation: propagate to children)
342 	bool mouseMotionEvent(Vector2i p, Vector2i rel, MouseButton button, int modifiers)
343 	{
344 		foreach_reverse(it; mChildren)
345 		{
346 			Widget child = it;
347 			if (!child.visible)
348 				continue;
349 			const contained = child.contains(p - mPos);
350 			const prevContained = child.contains(p - mPos - rel);
351 			if (contained != prevContained)
352 				child.mouseEnterEvent(p, contained);
353 			if ((contained || prevContained) &&
354 				child.mouseMotionEvent(p - mPos, rel, button, modifiers))
355 				return true;
356 		}
357 		return false;
358 	}
359 
360 	/// Handle a mouse drag event (default implementation: do nothing)
361 	bool mouseDragEvent(Vector2i p, Vector2i rel, MouseButton button, int modifiers)
362 	{
363 		return false;
364 	}
365 
366 	/// Handle a mouse enter/leave event (default implementation: record this fact, but do nothing)
367 	bool mouseEnterEvent(Vector2i p, bool enter)
368 	{
369 		mMouseFocus = enter;
370 		return false;
371 	}
372 
373 	/// Handle a mouse scroll event (default implementation: propagate to children)
374 	bool scrollEvent(Vector2i p, Vector2f rel)
375 	{
376 		foreach_reverse (child; mChildren)
377 		{
378 			if (!child.visible)
379 				continue;
380 			if (child.contains(p - mPos) && child.scrollEvent(p - mPos, rel))
381 				return true;
382 		}
383 		return false;
384 	}
385 
386 	/// Handle a focus change event (default implementation: record the focus status, but do nothing)
387 	bool focusEvent(bool focused)
388 	{
389 		mFocused = focused;
390 		return false;
391 	}
392 
393 	/// Handle a keyboard event (default implementation: do nothing)
394 	bool keyboardEvent(int key, int scancode, KeyAction action, int modifiers)
395 	{
396 		return false;
397 	}
398 
399 	/// Handle text input (UTF-32 format) (default implementation: do nothing)
400 	bool keyboardCharacterEvent(dchar codepoint)
401 	{
402 		return false;
403 	}
404 
405 	/// Compute the preferred size of the widget
406 	Vector2i preferredSize(NanoContext ctx) const
407 	{
408 		if (mLayout)
409 			return mLayout.preferredSize(ctx, this);
410 		else
411 			return mSize;
412 	}
413 
414 	/// Compute the preferred size of the widget considering its child except
415 	/// skipped one (for example button panel of window)
416 	final Vector2i preferredSize(NanoContext ctx, const Widget skipped) const
417 	{
418 		if (mLayout)
419 			return mLayout.preferredSize(ctx, this, skipped);
420 		else
421 			return mSize;
422 	}
423 
424 	/// Invoke the associated layout generator to properly place child widgets, if any
425 	void performLayout(NanoContext ctx)
426 	{
427 		if (mLayout) {
428 			mLayout.performLayout(ctx, this);
429 		} else {
430 			foreach(c; mChildren) {
431 				Vector2i pref = c.preferredSize(ctx), fix = c.fixedSize();
432 				c.size(Vector2i(
433 					fix[0] ? fix[0] : pref[0],
434 					fix[1] ? fix[1] : pref[1]
435 				));
436 				c.performLayout(ctx);
437 			}
438 		}
439 	}
440 
441 	/// Draw the widget (and all child widgets)
442 	void draw(ref NanoContext ctx)
443 	{
444 		version(NANOGUI_SHOW_WIDGET_BOUNDS)
445 		{
446 			ctx.strokeWidth(1.0f);
447 			ctx.beginPath;
448 			ctx.rect(mPos.x + 1.0f, mPos.y + 0.0f, mSize.x - 1, mSize.y - 1);
449 			ctx.strokeColor(Color(255, 0, 0, 255));
450 			ctx.stroke;
451 		}
452 
453 		if (mChildren.length == 0)
454 			return;
455 
456 		ctx.save;
457 		ctx.translate(mPos.x, mPos.y);
458 		foreach(child; mChildren)
459 		{
460 			if (child.visible)
461 			{
462 				ctx.save;
463 				scope(exit) ctx.restore;
464 				ctx.intersectScissor(child.mPos.x, child.mPos.y, child.mSize.x, child.mSize.y);
465 				child.draw(ctx);
466 			}
467 		}
468 		ctx.restore;
469 	}
470 
471 // // Save the state of the widget into the given \ref Serializer instance
472 //virtual void save(Serializer &s) const;
473 
474 // // Restore the state of the widget from the given \ref Serializer instance
475 //virtual bool load(Serializer &s);
476 
477 protected:
478 	/// Free all resources used by the widget and any children
479 	~this()
480 	{
481 //foreach(child; mChildren) {
482 //    if (child)
483 //        child.decRef();
484 //}
485 	}
486 
487 	/**
488 	 * Convenience definition for subclasses to get the full icon scale for this
489 	 * class of Widget.  It simple returns the value
490 	 * `mTheme.mIconScale * this.mIconExtraScale`.
491 	 *
492 	 * See_also:
493 	 *     `Theme.mIconScale` and `Widget.mIconExtraScale`.  This tiered scaling
494 	 *     strategy may not be appropriate with fonts other than `entypo.ttf`.
495 	 */
496 	pragma(inline, true)
497 	float icon_scale() const { return mTheme.mIconScale * mIconExtraScale; }
498 
499 	Widget mParent;
500 	Theme mTheme;
501 	Layout mLayout;
502 	string mId;
503 	Vector2i mPos, mSize, mFixedSize;
504 	Array!Widget mChildren;
505 
506 	/**
507 	 * Whether or not this Widget is currently visible.  When a Widget is not
508 	 * currently visible, no time is wasted executing its drawing method.
509 	 */
510 	bool mVisible;
511 
512 	/**
513 	 * Whether or not this Widget is currently enabled.  Various different kinds
514 	 * of derived types use this to determine whether or not user input will be
515 	 * accepted.  For example, when ``mEnabled == false``, the state of a
516 	 * CheckBox cannot be changed, or a TextBox will not allow new input.
517 	 */
518 	bool mEnabled;
519 	bool mFocused, mMouseFocus;
520 	string mTooltip;
521 	int mFontSize;
522 
523 	/**
524 	 * The amount of extra icon scaling used in addition the the theme's
525 	 * default icon font scale.  Default value is ``1.0``, which implies
526 	 * that `icon_scale` simply returns the value of `nanogui.Theme.mIconScale`.
527 	 *
528 	 * Most widgets do not need extra scaling, but some (e.g., `CheckBox`, `TextBox`)
529 	 * need to adjust the Theme's default icon scaling
530 	 * `nanogui.Theme.mIconScale` to properly display icons within their
531 	 * bounds (upscale, or downscale).
532 	 *
533 	 * Summary:
534 	 *
535 	 *    When using `nvgFontSize` for icons in subclasses, make sure to call
536 	 *    the `icon_scale` function.  Expected usage when *drawing* icon fonts
537 	 *    is something like:
538 	 *
539 	 * ---
540 	 *
541 	 *       void draw(NanoContext ctx)
542 	 *       {
543 	 *           // fontSize depends on the kind of `Widget`.  Search for `FontSize`
544 	 *           // in the `Theme` class (e.g., standard vs button)
545 	 *           float ih = fontSize;
546 	 *           // assuming your Widget has a declared `mIcon`
547 	 *           if (isFontIcon(mIcon)) {
548 	 *               ih *= icon_scale();
549 	 *               ctx.fontFace("icons");
550 	 *               ctx.fontSize(ih);
551 	 *               /// remaining drawing code (see button.d for more)
552 	 *           }
553 	 *       }
554 	 * ---
555 	 */
556 	float mIconExtraScale;
557 	Cursor mCursor;
558 }