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 }