1 ///
2 module nanogui.window;
3 
4 /*
5 	nanogui/window.d -- Top-level window widget
6 
7 	NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
8 	The widget drawing code is based on the NanoVG demo application
9 	by Mikko Mononen.
10 
11 	All rights reserved. Use of this source code is governed by a
12 	BSD-style license that can be found in the LICENSE.txt file.
13 */
14 
15 import nanogui.widget;
16 import nanogui.common : Vector2i, Vector2f, MouseButton;
17 
18 /**
19  * Top-level window widget.
20  */
21 class Window : Widget
22 {
23 public:
24 	this(Widget parent, string title = "Untitled")
25 	{    	
26 		super(parent);
27 		mTitle = title;
28 		mButtonPanel = null;
29 		mModal = false;
30 		mDrag = false;
31 	}
32 
33 	/// Return the window title
34 	final string title() const { return mTitle; }
35 	/// Set the window title
36 	final void title(string title) { mTitle = title; }
37 
38 	/// Is this a model dialog?
39 	final bool modal() const { return mModal; }
40 	/// Set whether or not this is a modal dialog
41 	final void modal(bool modal) { mModal = modal; }
42 
43 	/// Return the panel used to house window buttons
44 	final Widget buttonPanel()
45 	{
46 		import nanogui.layout : BoxLayout, Orientation, Alignment;
47 		if (!mButtonPanel) {
48 			mButtonPanel = new Widget(this);
49 			mButtonPanel.layout(new BoxLayout(Orientation.Horizontal, Alignment.Middle, 0, 4));
50 		}
51 		return mButtonPanel;
52 	}
53 
54 	/// Dispose the window
55 	final void dispose();
56 
57 	/// Center the window in the current `Screen`
58 	final void center();
59 
60 	/// Draw the window
61 	override void draw(NVGContext nvg)
62 	{
63 		assert (mTheme);
64 		int ds = mTheme.mWindowDropShadowSize, cr = mTheme.mWindowCornerRadius;
65 		int hh = mTheme.mWindowHeaderHeight;
66 
67 		/* Draw window */
68 		nvg.save;
69 		nvg.beginPath;
70 		nvg.roundedRect(mPos.x, mPos.y, mSize.x, mSize.y, cr);
71 
72 		nvg.fillColor(mMouseFocus ? mTheme.mWindowFillFocused
73 									  : mTheme.mWindowFillUnfocused);
74 		nvg.fill;
75 
76 
77 		/* Draw a drop shadow */
78 		NVGPaint shadowPaint = nvg.boxGradient(
79 			mPos.x, mPos.y, mSize.x, mSize.y, cr*2, ds*2,
80 			mTheme.mDropShadow, mTheme.mTransparent);
81 
82 		nvg.save;
83 		nvg.resetScissor;
84 		nvg.beginPath;
85 		nvg.rect(mPos.x-ds,mPos.y-ds, mSize.x+2*ds, mSize.y+2*ds);
86 		nvg.roundedRect(mPos.x, mPos.y, mSize.x, mSize.y, cr);
87 		nvg.pathWinding(NVGSolidity.Hole);
88 		nvg.fillPaint(shadowPaint);
89 		nvg.fill;
90 		nvg.restore;
91 
92 		if (mTitle.length)
93 		{
94 			/* Draw header */
95 			NVGPaint headerPaint = nvg.linearGradient(
96 				mPos.x, mPos.y, mPos.x,
97 				mPos.y + hh,
98 				mTheme.mWindowHeaderGradientTop,
99 				mTheme.mWindowHeaderGradientBot);
100 
101 			nvg.beginPath;
102 			nvg.roundedRect(mPos.x, mPos.y, mSize.x, hh, cr);
103 
104 			nvg.fillPaint(headerPaint);
105 			nvg.fill;
106 
107 			nvg.beginPath;
108 			nvg.roundedRect(mPos.x, mPos.y, mSize.x, hh, cr);
109 			nvg.strokeColor(mTheme.mWindowHeaderSepTop);
110 
111 			nvg.save;
112 			nvg.intersectScissor(mPos.x, mPos.y, mSize.x, 0.5f);
113 			nvg.stroke;
114 			nvg.restore;
115 
116 			nvg.beginPath;
117 			nvg.moveTo(mPos.x + 0.5f, mPos.y + hh - 1.5f);
118 			nvg.lineTo(mPos.x + mSize.x - 0.5f, mPos.y + hh - 1.5);
119 			nvg.strokeColor(mTheme.mWindowHeaderSepBot);
120 			nvg.stroke;
121 
122 			nvg.fontSize(18.0f);
123 			nvg.fontFace("sans-bold");
124 			auto algn = NVGTextAlign();
125 			algn.center = true;
126 			algn.middle = true;
127 			nvg.textAlign(algn);
128 
129 			nvg.fontBlur(2);
130 			nvg.fillColor(mTheme.mDropShadow);
131 			nvg.text(mPos.x + mSize.x / 2,
132 					mPos.y + hh / 2, mTitle);
133 
134 			nvg.fontBlur(0);
135 			nvg.fillColor(mFocused ? mTheme.mWindowTitleFocused
136 									   : mTheme.mWindowTitleUnfocused);
137 			nvg.text(mPos.x + mSize.x / 2, mPos.y + hh / 2 - 1,
138 					mTitle);
139 		}
140 
141 		nvg.restore;
142 		Widget.draw(nvg);
143 	}
144 	/// Handle window drag events
145 	override bool mouseDragEvent(Vector2i p, Vector2i rel, MouseButton button, int modifiers)
146 	{
147 		import std.algorithm : min, max;
148 
149 		if (mDrag && (button & (1 << MouseButton.Left)) != 0) {
150 			mPos += rel;
151 			{
152 				// mPos = mPos.cwiseMax(Vector2i::Zero());
153 				mPos[0] = max(mPos[0], 0);
154 				mPos[1] = max(mPos[1], 0);
155 			}
156 			{
157 				// mPos = mPos.cwiseMin(parent()->size() - mSize);
158 				auto other = parent.size - mSize;
159 				mPos[0] = min(mPos[0], other[0]);
160 				mPos[1] = min(mPos[1], other[1]);
161 			}
162 			return true;
163 		}
164 		return false;
165 	}
166 	/// Handle mouse events recursively and bring the current window to the top
167 	override bool mouseButtonEvent(Vector2i p, MouseButton button, bool down, int modifiers)
168 	{
169 		if (super.mouseButtonEvent(p, button, down, modifiers))
170 			return true;
171 		if (button == MouseButton.Left)
172 		{
173 			mDrag = down && (p.y - mPos.y) < mTheme.mWindowHeaderHeight;
174 			return true;
175 		}
176 		return false;
177 	}
178 	/// Accept scroll events and propagate them to the widget under the mouse cursor
179 	override bool scrollEvent(Vector2i p, Vector2f rel)
180 	{
181 		Widget.scrollEvent(p, rel);
182 		return true;
183 	}
184 
185 	/// Compute the preferred size of the widget
186 	override Vector2i preferredSize(NVGContext nvg) const
187 	{
188 		Vector2i result = Widget.preferredSize(nvg, mButtonPanel);
189 
190 		nvg.fontSize(18.0f);
191 		nvg.fontFace("sans-bold");
192 		float[4] bounds;
193 		nvg.textBounds(0, 0, mTitle, bounds);
194 
195 		if (result.x < bounds[2]-bounds[0] + 20)
196 			result.x = cast(int) (bounds[2]-bounds[0] + 20);
197 		if (result.y < bounds[3]-bounds[1])
198 			result.y = cast(int) (bounds[3]-bounds[1]);
199 
200 		return result;
201 	}
202 	/// Invoke the associated layout generator to properly place child widgets, if any
203 	override void performLayout(NVGContext nvg)
204 	{
205 		if (!mButtonPanel) {
206 			Widget.performLayout(nvg);
207 		} else {
208 			mButtonPanel.visible(false);
209 			Widget.performLayout(nvg);
210 			foreach (w; mButtonPanel.children) {
211 				w.fixedSize(Vector2i(22, 22));
212 				w.fontSize(15);
213 			}
214 			mButtonPanel.visible(true);
215 			mButtonPanel.size(Vector2i(width(), 22));
216 			mButtonPanel.position(Vector2i(width() - (mButtonPanel.preferredSize(nvg).x + 5), 3));
217 			mButtonPanel.performLayout(nvg);
218 		}
219 	}
220 //override void save(Serializer &s) const;
221 //override bool load(Serializer &s);
222 public:
223 	/// Internal helper function to maintain nested window position values; overridden in \ref Popup
224 	void refreshRelativePlacement()
225 	{
226 		/* Overridden in \ref Popup */
227 	}
228 protected:
229 
230 	string mTitle;
231 	Widget mButtonPanel;
232 	bool mModal;
233 	bool mDrag;
234 }