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.ButtonDown) {
178 // clicked outside - close popup
179 close();
180 return false;
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 }