1 // Aseprite UI Library
2 // Copyright (C) 2001-2017 David Capello
3 //
4 // This file is released under the terms of the MIT license.
5 // Read LICENSE.txt for more information.
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include "gfx/size.h"
12 #include "ui/graphics.h"
13 #include "ui/intern.h"
14 #include "ui/size_hint_event.h"
15 #include "ui/theme.h"
16 #include "ui/ui.h"
17
18 namespace ui {
19
20 using namespace gfx;
21
PopupWindow(const std::string & text,const ClickBehavior clickBehavior,const EnterBehavior enterBehavior,const bool withCloseButton)22 PopupWindow::PopupWindow(const std::string& text,
23 const ClickBehavior clickBehavior,
24 const EnterBehavior enterBehavior,
25 const bool withCloseButton)
26 : Window(text.empty() ? WithoutTitleBar: WithTitleBar, text)
27 , m_clickBehavior(clickBehavior)
28 , m_enterBehavior(enterBehavior)
29 , m_filtering(false)
30 , m_fixed(false)
31 {
32 setSizeable(false);
33 setMoveable(false);
34 setWantFocus(false);
35 setAlign(LEFT | TOP);
36
37 if (!withCloseButton) {
38 // Remove close button
39 for (auto child : children()) {
40 if (child->type() == kWindowCloseButtonWidget) {
41 delete child;
42 break;
43 }
44 }
45 }
46
47 initTheme();
48 }
49
~PopupWindow()50 PopupWindow::~PopupWindow()
51 {
52 stopFilteringMessages();
53 }
54
setHotRegion(const gfx::Region & region)55 void PopupWindow::setHotRegion(const gfx::Region& region)
56 {
57 startFilteringMessages();
58
59 m_hotRegion = region;
60 }
61
setClickBehavior(ClickBehavior behavior)62 void PopupWindow::setClickBehavior(ClickBehavior behavior)
63 {
64 m_clickBehavior = behavior;
65 }
66
setEnterBehavior(EnterBehavior behavior)67 void PopupWindow::setEnterBehavior(EnterBehavior behavior)
68 {
69 m_enterBehavior = behavior;
70 }
71
makeFloating()72 void PopupWindow::makeFloating()
73 {
74 stopFilteringMessages();
75 setMoveable(true);
76 m_fixed = false;
77
78 onMakeFloating();
79 }
80
makeFixed()81 void PopupWindow::makeFixed()
82 {
83 startFilteringMessages();
84 setMoveable(false);
85 m_fixed = true;
86
87 onMakeFixed();
88 }
89
onProcessMessage(Message * msg)90 bool PopupWindow::onProcessMessage(Message* msg)
91 {
92 switch (msg->type()) {
93
94 // There are cases where startFilteringMessages() is called when a
95 // kCloseMessage for this same PopupWindow is enqueued. Processing
96 // the kOpenMessage we ensure that the popup will be filtering
97 // messages if it's needed when it's visible (as kCloseMessage and
98 // kOpenMessage must be enqueued in the correct order).
99 case kOpenMessage:
100 if (!isMoveable())
101 startFilteringMessages();
102 break;
103
104 case kCloseMessage:
105 stopFilteringMessages();
106 break;
107
108 case kMouseLeaveMessage:
109 if (m_hotRegion.isEmpty() && m_fixed)
110 closeWindow(nullptr);
111 break;
112
113 case kKeyDownMessage:
114 if (m_filtering) {
115 KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
116 KeyScancode scancode = keymsg->scancode();
117
118 if (scancode == kKeyEsc)
119 closeWindow(nullptr);
120
121 if (m_enterBehavior == EnterBehavior::CloseOnEnter &&
122 (scancode == kKeyEnter ||
123 scancode == kKeyEnterPad)) {
124 closeWindow(this);
125 return true;
126 }
127
128 // If the message came from a filter, we don't send it back to
129 // the default Window processing (which will send the message
130 // to the Manager). In this way, the focused children can
131 // process the kKeyDownMessage.
132 if (msg->fromFilter())
133 return false;
134 }
135 break;
136
137 case kMouseDownMessage:
138 if (m_filtering &&
139 manager()->getTopWindow() == this) {
140 gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
141
142 switch (m_clickBehavior) {
143
144 // If the user click outside the window, we have to close
145 // the tooltip window.
146 case ClickBehavior::CloseOnClickInOtherWindow: {
147 Widget* picked = pick(mousePos);
148 if (!picked || picked->window() != this) {
149 closeWindow(NULL);
150 }
151 break;
152 }
153
154 case ClickBehavior::CloseOnClickOutsideHotRegion:
155 if (!m_hotRegion.contains(mousePos)) {
156 closeWindow(NULL);
157 }
158 break;
159 }
160 }
161 break;
162
163 case kMouseMoveMessage:
164 if (m_fixed &&
165 !m_hotRegion.isEmpty() &&
166 manager()->getCapture() == NULL) {
167 gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
168
169 // If the mouse is outside the hot-region we have to close the
170 // window.
171 if (!m_hotRegion.contains(mousePos))
172 closeWindow(NULL);
173 }
174 break;
175
176 }
177
178 return Window::onProcessMessage(msg);
179 }
180
onHitTest(HitTestEvent & ev)181 void PopupWindow::onHitTest(HitTestEvent& ev)
182 {
183 Window::onHitTest(ev);
184
185 Widget* picked = manager()->pick(ev.point());
186 if (picked) {
187 WidgetType type = picked->type();
188 if (type == kWindowWidget && picked == this) {
189 if (isSizeable() && (ev.hit() == HitTestBorderNW ||
190 ev.hit() == HitTestBorderN ||
191 ev.hit() == HitTestBorderNE ||
192 ev.hit() == HitTestBorderE ||
193 ev.hit() == HitTestBorderSE ||
194 ev.hit() == HitTestBorderS ||
195 ev.hit() == HitTestBorderSW ||
196 ev.hit() == HitTestBorderW)) {
197 // Use the hit value from Window::onHitTest()
198 return;
199 }
200 else {
201 ev.setHit(isMoveable() ? HitTestCaption: HitTestClient);
202 }
203 }
204 else if (type == kBoxWidget ||
205 type == kLabelWidget ||
206 type == kLinkLabelWidget ||
207 type == kGridWidget ||
208 type == kSeparatorWidget) {
209 ev.setHit(isMoveable() ? HitTestCaption: HitTestClient);
210 }
211 }
212 }
213
startFilteringMessages()214 void PopupWindow::startFilteringMessages()
215 {
216 if (!m_filtering) {
217 m_filtering = true;
218
219 Manager* manager = Manager::getDefault();
220 manager->addMessageFilter(kMouseMoveMessage, this);
221 manager->addMessageFilter(kMouseDownMessage, this);
222 manager->addMessageFilter(kKeyDownMessage, this);
223 }
224 }
225
stopFilteringMessages()226 void PopupWindow::stopFilteringMessages()
227 {
228 if (m_filtering) {
229 m_filtering = false;
230
231 Manager* manager = Manager::getDefault();
232 manager->removeMessageFilter(kMouseMoveMessage, this);
233 manager->removeMessageFilter(kMouseDownMessage, this);
234 manager->removeMessageFilter(kKeyDownMessage, this);
235 }
236 }
237
onMakeFloating()238 void PopupWindow::onMakeFloating()
239 {
240 // Do nothing
241 }
242
onMakeFixed()243 void PopupWindow::onMakeFixed()
244 {
245 // Do nothing
246 }
247
TransparentPopupWindow(ClickBehavior clickBehavior)248 TransparentPopupWindow::TransparentPopupWindow(ClickBehavior clickBehavior)
249 : PopupWindow("", clickBehavior)
250 {
251 setTransparent(true);
252 initTheme();
253 }
254
onInitTheme(InitThemeEvent & ev)255 void TransparentPopupWindow::onInitTheme(InitThemeEvent& ev)
256 {
257 PopupWindow::onInitTheme(ev);
258 // TODO fix this, if we use alpha=0 (gfx::ColorNone), we get
259 // "window_face" color as background the transparent popup window.
260 //setBgColor(gfx::ColorNone);
261 setBgColor(gfx::rgba(0, 0, 0, 1));
262 }
263
264 } // namespace ui
265