1 ///
2 module nanogui.popup;
3 /*
4     nanogui/popup.h -- Simple popup widget which is attached to another given
5     window (can be nested)
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.window : Window;
16 import nanogui.widget : Widget, NanoContext;
17 import nanogui.common : Vector2i;
18 
19 /**
20  * Popup window for combo boxes, popup buttons, nested dialogs etc.
21  *
22  * Usually the Popup instance is constructed by another widget (e.g. `PopupButton`)
23  * and does not need to be created by hand.
24  */
25 class Popup : Window
26 {
27 public:
28     enum Side { Left = 0, Right }
29 
30     /// Create a new popup parented to a screen (first argument) and a parent window
31     this(Widget parent, Window parentWindow)
32     {
33         super(parent, "");
34         mParentWindow = parentWindow;
35         mAnchorHeight = 30;
36         mAnchorPos    = Vector2i(0, 0);
37         mSide         = Side.Right;
38     }
39 
40     /// Return the anchor position in the parent window; the placement of the popup is relative to it
41     final void anchorPos(Vector2i anchorPos) { mAnchorPos = anchorPos; }
42     /// Set the anchor position in the parent window; the placement of the popup is relative to it
43     final Vector2i anchorPos() const { return mAnchorPos; }
44 
45     /// Set the anchor height; this determines the vertical shift relative to the anchor position
46     final void anchorHeight(int anchorHeight) { mAnchorHeight = anchorHeight; }
47     /// Return the anchor height; this determines the vertical shift relative to the anchor position
48     final int anchorHeight() const { return mAnchorHeight; }
49 
50     /// Set the side of the parent window at which popup will appear
51     final void setSide(Side popupSide) { mSide = popupSide; }
52     /// Return the side of the parent window at which popup will appear
53     final Side side() const { return mSide; }
54 
55     /// Return the parent window of the popup
56     final Window parentWindow() { return mParentWindow; }
57     /// Return the parent window of the popup
58     final parentWindow() const { return mParentWindow; }
59 
60     /// Invoke the associated layout generator to properly place child widgets, if any
61     override void performLayout(NanoContext ctx)
62     {
63         if (mLayout || mChildren.length != 1) {
64             Widget.performLayout(ctx);
65         } else {
66             mChildren[0].position(Vector2i(0, 0));
67             mChildren[0].size(mSize);
68             mChildren[0].performLayout(ctx);
69         }
70         if (mSide == Side.Left)
71             mAnchorPos[0] -= size[0];
72     }
73 
74     /// Draw the popup window
75     override void draw(ref NanoContext ctx)
76     {
77         import arsd.nanovega;
78         import nanogui.common;
79 
80         refreshRelativePlacement();
81 
82         if (!mVisible)
83             return;
84 
85         int ds = mTheme.mWindowDropShadowSize, cr = mTheme.mWindowCornerRadius;
86 
87         ctx.save;
88         ctx.resetScissor;
89 
90         /* Draw a drop shadow */
91         NVGPaint shadowPaint = ctx.boxGradient(
92             mPos.x, mPos.y, mSize.x, mSize.y, cr*2, ds*2,
93             mTheme.mDropShadow, mTheme.mTransparent);
94 
95         ctx.beginPath;
96         ctx.rect(mPos.x-ds,mPos.y-ds, mSize.x+2*ds, mSize.y+2*ds);
97         ctx.roundedRect(mPos.x, mPos.y, mSize.x, mSize.y, cr);
98         ctx.pathWinding(NVGSolidity.Hole);
99         ctx.fillPaint(shadowPaint);
100         ctx.fill;
101 
102         /* Draw window */
103         ctx.beginPath;
104         ctx.roundedRect(mPos.x, mPos.y, mSize.x, mSize.y, cr);
105 
106         Vector2i base = mPos + Vector2i(0, mAnchorHeight);
107         int sign = -1;
108         if (mSide == Side.Left) {
109             base.x += mSize.x;
110             sign = 1;
111         }
112 
113         ctx.moveTo(base.x + 15*sign, base.y);
114         ctx.lineTo(base.x - 1*sign, base.y - 15);
115         ctx.lineTo(base.x - 1*sign, base.y + 15);
116 
117         ctx.fillColor(mTheme.mWindowPopup);
118         ctx.fill;
119         ctx.restore;
120 
121         Widget.draw(ctx);
122     }
123 
124     //virtual void save(Serializer &s) const override;
125     //virtual bool load(Serializer &s) override;
126 protected:
127     /// Internal helper function to maintain nested window position values
128     override void refreshRelativePlacement()
129     {
130         mParentWindow.refreshRelativePlacement();
131         mVisible &= mParentWindow.visibleRecursive;
132         mPos = mParentWindow.position + mAnchorPos - Vector2i(0, mAnchorHeight);
133     }
134 
135     Window mParentWindow;
136     Vector2i mAnchorPos;
137     int mAnchorHeight;
138     Side mSide;
139 }