1 // Written in the D programming language.
2 
3 /**
4 This module contains popup widgets implementation.
5 
6 Popups appear above other widgets inside window.
7 
8 Useful for popup menus, notification popups, etc.
9 
10 Synopsis:
11 
12 ----
13 import dlangui.widgets.popup;
14 
15 ----
16 
17 Copyright: Vadim Lopatin, 2014
18 License:   Boost License 1.0
19 Authors:   Vadim Lopatin, coolreader.org@gmail.com
20 */
21 module dlangui.widgets.popup;
22 
23 import dlangui.widgets.widget;
24 import dlangui.widgets.layouts;
25 import dlangui.core.signals;
26 import dlangui.platforms.common.platform;
27 
28 /// popup alignment option flags
29 enum PopupAlign : uint {
30     /// center popup around anchor widget center
31     Center = 1,
32     /// place popup below anchor widget close to lower bound
33     Below = 2,
34     /// place popup below anchor widget close to lower bound
35     Above = 16,
36     /// place popup below anchor widget close to right bound (when no space enough, align near left bound)
37     Right = 4,
38     /// align to specified point
39     Point = 8,
40     /// if popup content size is less than anchor's size, increase it to anchor size
41     FitAnchorSize = 16,
42 }
43 
44 struct PopupAnchor {
45     Widget widget;
46     int x;
47     int y;
48     uint  alignment = PopupAlign.Center;
49 }
50 
51 /// popup behavior flags - for PopupWidget.flags property
52 enum PopupFlags : uint {
53     /// close popup when mouse button clicked outside of its bounds
54     CloseOnClickOutside = 1,
55     /// modal popup - keypresses and mouse events can be routed to this popup only
56     Modal = 2,
57     /// close popup when mouse is moved outside this popup
58     CloseOnMouseMoveOutside = 4,
59 }
60 
61 /** interface - slot for onPopupCloseListener */
62 interface OnPopupCloseHandler {
63     void onPopupClosed(PopupWidget source);
64 }
65 
66 /// popup widget container
67 class PopupWidget : LinearLayout {
68     protected PopupAnchor _anchor;
69     protected bool _modal;
70 
71     protected uint _flags;
72     /** popup close signal */
73     Signal!OnPopupCloseHandler popupClosed;
74     //protected void delegate(PopupWidget popup) _onPopupCloseListener;
75     /// popup close listener (called right before closing)
76     //@property void delegate(PopupWidget popup) onPopupCloseListener() { return _onPopupCloseListener; }
77     /// set popup close listener (to call right before closing)
78     //@property PopupWidget onPopupCloseListener(void delegate(PopupWidget popup) listener) { _onPopupCloseListener = listener; return this; }
79 
80     /// returns popup behavior flags (combination of PopupFlags)
81     @property uint flags() { return _flags; }
82     /// set popup behavior flags (combination of PopupFlags)
83     @property PopupWidget flags(uint flags) { _flags = flags; return this; }
84 
85     /// access to popup anchor
86     @property ref PopupAnchor anchor() { return _anchor; }
87     /// returns true if popup is modal
88     bool modal() { return _modal; }
89     /// set modality flag
90     PopupWidget modal(bool modal) { _modal = modal; return this; }
91 
92     /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
93     override void measure(int parentWidth, int parentHeight) {
94         super.measure(parentWidth, parentHeight);
95     }
96     /// close and destroy popup
97     void close() {
98         window.removePopup(this);
99     }
100 
101     /// just call on close listener
102     void onClose() {
103         if (popupClosed.assigned)
104             popupClosed(this);
105     }
106 
107     /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
108     override void layout(Rect rc) {
109         if (visibility == Visibility.Gone) {
110             return;
111         }
112         int w = measuredWidth;
113         int h = measuredHeight;
114         if (w > rc.width)
115             w = rc.width;
116         if (h > rc.height)
117             h = rc.height;
118 
119         Rect anchorrc;
120         if (anchor.widget !is null)
121             anchorrc = anchor.widget.pos;
122         else
123             anchorrc = rc;
124 
125         Rect r;
126         Point anchorPt;
127 
128         if (anchor.alignment & PopupAlign.Point) {
129             r.left = anchor.x;
130             r.top = anchor.y;
131             if (anchor.alignment & PopupAlign.Center) {
132                 // center around center of anchor widget
133                 r.left -= w / 2;
134                 r.top -= h / 2;
135             } else if (anchor.alignment & PopupAlign.Below) {
136             } else if (anchor.alignment & PopupAlign.Above) {
137                 r.top -= h;
138             } else if (anchor.alignment & PopupAlign.Right) {
139             }
140         } else {
141             if (anchor.alignment & PopupAlign.Center) {
142                 // center around center of anchor widget
143                 r.left = anchorrc.middlex - w / 2;
144                 r.top = anchorrc.middley - h / 2;
145             } else if (anchor.alignment & PopupAlign.Below) {
146                 r.left = anchorrc.left;
147                 r.top = anchorrc.bottom;
148             } else if (anchor.alignment & PopupAlign.Above) {
149                 r.left = anchorrc.left;
150                 r.top = anchorrc.top - h;
151             } else if (anchor.alignment & PopupAlign.Right) {
152                 r.left = anchorrc.right;
153                 r.top = anchorrc.top;
154             }
155             if (anchor.alignment & PopupAlign.FitAnchorSize)
156                 if (w < anchorrc.width)
157                     w = anchorrc.width;
158         }
159         r.right = r.left + w;
160         r.bottom = r.top + h;
161         r.moveToFit(rc);
162         super.layout(r);
163     }
164 
165     this(Widget content, Window window) {
166         super("POPUP");
167         _window = window;
168         //styleId = "POPUP_MENU";
169         addChild(content);
170     }
171 
172     /// called for mouse activity outside shown popup bounds
173     bool onMouseEventOutside(MouseEvent event) {
174         if (visibility != Visibility.Visible)
175             return false;
176         if (_flags & PopupFlags.CloseOnClickOutside) {
177             if (event.action == MouseAction.ButtonUp) {
178                 // clicked outside - close popup
179                 close();
180                 return true;
181             }
182         }
183         if (_flags & PopupFlags.CloseOnMouseMoveOutside) {
184             if (event.action == MouseAction.Move || event.action == MouseAction.Wheel) {
185                 int threshold = 3;
186                 if (event.x < _pos.left - threshold || event.x > _pos.right + threshold || event.y < _pos.top - threshold || event.y > _pos.bottom + threshold) {
187                     Log.d("Closing popup due to PopupFlags.CloseOnMouseMoveOutside flag");
188                     close();
189                     return false;
190                 }
191             }
192         }
193         return false;
194     }
195 }