1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 *
19 */
20
21 #include "Window.h"
22
23 #include "GUI/GUIScriptInterface.h"
24 #include "Interface.h"
25 #include "ScrollBar.h"
26
27 namespace GemRB {
28
Window(const Region & frame,WindowManager & mgr)29 Window::Window(const Region& frame, WindowManager& mgr)
30 : ScrollView(frame), manager(mgr)
31 {
32 focusView = NULL;
33 trackingView = NULL;
34 hoverView = NULL;
35 lastMouseMoveTime = GetTicks();
36
37 SetFlags(DestroyOnClose, OP_OR);
38 // default ingame windows to frameless
39 if (core->HasCurrentArea()) {
40 SetFlags(Borderless, OP_OR);
41 }
42 RecreateBuffer();
43 }
44
Close()45 void Window::Close()
46 {
47 // fire the onclose handler prior to actually invalidating the window
48 if (eventHandlers[Closed]) {
49 eventHandlers[Closed](this);
50 }
51
52 if (flags&DestroyOnClose) {
53 ClearScriptingRefs();
54 manager.CloseWindow(this);
55 } else {
56 // somebody wants to keep a handle to this window around to display it later
57 manager.OrderBack(this);
58 SetVisible(false);
59 }
60
61 trackingView = NULL;
62 hoverView = NULL;
63 }
64
FocusLost()65 void Window::FocusLost()
66 {
67 if (eventHandlers[LostFocus]) {
68 eventHandlers[LostFocus](this);
69 }
70 }
71
FocusGained()72 void Window::FocusGained()
73 {
74 if (eventHandlers[GainedFocus]) {
75 eventHandlers[GainedFocus](this);
76 }
77 }
78
HasFocus() const79 bool Window::HasFocus() const
80 {
81 return manager.GetFocusWindow() == this;
82 }
83
DisplayModal(WindowManager::ModalShadow shadow)84 bool Window::DisplayModal(WindowManager::ModalShadow shadow)
85 {
86 return manager.PresentModalWindow(this, shadow);
87 }
88
89 /** Add a Control in the Window */
SubviewAdded(View * view,View *)90 void Window::SubviewAdded(View* view, View* /*parent*/)
91 {
92 Control* ctrl = dynamic_cast<Control*>(view);
93 if (ctrl) {
94 Controls.insert(ctrl);
95 }
96
97 // MoviePlayer at least relies on this
98 if (focusView == NULL) {
99 TrySetFocus(view);
100 }
101 }
102
SubviewRemoved(View * subview,View * parent)103 void Window::SubviewRemoved(View* subview, View* parent)
104 {
105 Control* ctrl = dynamic_cast<Control*>(subview);
106 if (ctrl) {
107 Controls.erase(ctrl);
108 }
109
110 if (subview->ContainsView(trackingView)) {
111 trackingView = NULL;
112 drag = NULL;
113 }
114
115 if (subview->ContainsView(hoverView)) {
116 hoverView = parent;
117 }
118
119 if (subview->ContainsView(focusView)) {
120 focusView->DidUnFocus();
121 focusView = NULL;
122 for (std::set<Control *>::iterator c = Controls.begin(); c != Controls.end(); ++c) {
123 Control* ctrl = *c;
124 if (TrySetFocus(ctrl) == ctrl) {
125 break;
126 }
127 }
128 }
129 }
130
SizeChanged(const Size &)131 void Window::SizeChanged(const Size& /*oldSize*/)
132 {
133 RecreateBuffer();
134 }
135
FlagsChanged(unsigned int oldflags)136 void Window::FlagsChanged(unsigned int oldflags)
137 {
138 if ((flags&AlphaChannel) != (oldflags&AlphaChannel)) {
139 RecreateBuffer();
140 }
141
142 if ((flags&View::Invisible) && focusView) {
143 focusView->DidUnFocus();
144 } else if ((oldflags&View::Invisible) && focusView) {
145 focusView->DidFocus();
146 }
147 }
148
RecreateBuffer()149 void Window::RecreateBuffer()
150 {
151 Video* video = core->GetVideoDriver();
152
153 Video::BufferFormat fmt = (flags&AlphaChannel) ? Video::BufferFormat::DISPLAY_ALPHA : Video::BufferFormat::DISPLAY;
154 backBuffer = video->CreateBuffer(frame, fmt);
155
156 // the entire window must be invalidated, because the new buffer is blank
157 // TODO: we *could* optimize this to instead blit the old buffer to the new one
158 MarkDirty();
159 }
160
DrawWithoutComposition()161 const VideoBufferPtr& Window::DrawWithoutComposition()
162 {
163 View::Draw();
164
165 core->GetVideoDriver()->PopDrawingBuffer();
166 return backBuffer;
167 }
168
WillDraw(const Region &,const Region &)169 void Window::WillDraw(const Region& /*drawFrame*/, const Region& /*clip*/)
170 {
171 backBuffer->SetOrigin(frame.Origin());
172 core->GetVideoDriver()->PushDrawingBuffer(backBuffer);
173 }
174
DidDraw(const Region &,const Region &)175 void Window::DidDraw(const Region& /*drawFrame*/, const Region& /*clip*/)
176 {
177 if (!core->InDebugMode(ID_WINDOWS)) return;
178
179 Video* video = core->GetVideoDriver();
180 video->SetScreenClip(nullptr);
181
182 auto lock = manager.DrawHUD();
183
184 if (focusView) {
185 Region r = focusView->ConvertRegionToScreen(Region(Point(), focusView->Dimensions()));
186 video->DrawRect(r, ColorWhite, false);
187 }
188
189 if (hoverView) {
190 Region r = hoverView->ConvertRegionToScreen(Region(Point(), hoverView->Dimensions()));
191 r.ExpandAllSides(-5);
192 video->DrawRect(r, ColorBlue, false);
193 }
194
195 if (trackingView) {
196 Region r = trackingView->ConvertRegionToScreen(Region(Point(), trackingView->Dimensions()));
197 r.ExpandAllSides(-10);
198 video->DrawRect(r, ColorRed, false);
199 }
200 }
201
Focus()202 void Window::Focus()
203 {
204 manager.FocusWindow(this);
205 }
206
SetFocused(View * ctrl)207 void Window::SetFocused(View* ctrl)
208 {
209 TrySetFocus(ctrl);
210 }
211
TooltipText() const212 String Window::TooltipText() const
213 {
214 if (hoverView) {
215 return hoverView->TooltipText();
216 }
217 return ScrollView::TooltipText();
218 }
219
Cursor() const220 Holder<Sprite2D> Window::Cursor() const
221 {
222 if (drag) {
223 return drag->cursor;
224 }
225
226 Holder<Sprite2D> cursor = ScrollView::Cursor();
227 if (cursor == NULL && hoverView) {
228 cursor = hoverView->Cursor();
229 }
230 return cursor;
231 }
232
IsDisabledCursor() const233 bool Window::IsDisabledCursor() const
234 {
235 bool isDisabled = ScrollView::IsDisabledCursor();
236 if (hoverView) {
237 // if either the window or view is in a disabled state the cursor will be
238 isDisabled = isDisabled || hoverView->IsDisabledCursor();
239 }
240 return isDisabled;
241 }
242
SetPosition(WindowPosition pos)243 void Window::SetPosition(WindowPosition pos)
244 {
245 // start at top left
246 Region newFrame(Point(), frame.Dimensions());
247 Size screen = manager.ScreenSize();
248
249 // adjust horizontal
250 if ((pos&PosHmid) == PosHmid) {
251 newFrame.x = (screen.w / 2) - (newFrame.w) / 2;
252 } else if (pos&PosRight) {
253 newFrame.x = screen.w - newFrame.w;
254 }
255
256 // adjust vertical
257 if ((pos&PosVmid) == PosVmid) {
258 newFrame.y = (screen.h / 2) - (newFrame.h) / 2;
259 } else if (pos&PosBottom) {
260 newFrame.y = screen.h - newFrame.h;
261 }
262 SetFrame(newFrame);
263 }
264
RedrawControls(const char * VarName,unsigned int Sum)265 void Window::RedrawControls(const char* VarName, unsigned int Sum)
266 {
267 for (std::set<Control *>::iterator c = Controls.begin(); c != Controls.end(); ++c) {
268 Control* ctrl = *c;
269 ctrl->UpdateState( VarName, Sum);
270 }
271 }
272
TrySetFocus(View * target)273 View* Window::TrySetFocus(View* target)
274 {
275 View* newFocus = focusView;
276 if (target && !target->CanLockFocus()) {
277 // target wont accept focus so dont bother unfocusing current
278 } else if (focusView && !focusView->CanUnlockFocus()) {
279 // current focus unwilling to reliquish
280 } else {
281 if (focusView)
282 focusView->DidUnFocus();
283
284 newFocus = target;
285
286 if (newFocus)
287 newFocus->DidFocus();
288 }
289 focusView = newFocus;
290
291 return newFocus;
292 }
293
IsDragable() const294 bool Window::IsDragable() const
295 {
296 if (trackingView != this)
297 {
298 return false;
299 }
300
301 return (flags&Draggable) ||
302 (EventMgr::ModState(GEM_MOD_CTRL) && EventMgr::MouseButtonState(GEM_MB_ACTION));
303 }
304
HitTest(const Point & p) const305 bool Window::HitTest(const Point& p) const
306 {
307 bool hit = View::HitTest(p);
308 if (hit == false){
309 // check the control list. we could make View::HitTest optionally recursive, but this is cheaper
310 for (std::set<Control *>::iterator c = Controls.begin(); c != Controls.end(); ++c) {
311 Control* ctrl = *c;
312 if (ctrl->IsVisible() && ctrl->View::HitTest(ctrl->ConvertPointFromWindow(p))) {
313 hit = true;
314 break;
315 }
316 }
317 }
318 return hit;
319 }
320
SetAction(Responder handler,const ActionKey & key)321 void Window::SetAction(Responder handler, const ActionKey& key)
322 {
323 eventHandlers[key.Value()] = std::move(handler);
324 }
325
PerformAction(const ActionKey & key)326 bool Window::PerformAction(const ActionKey& key)
327 {
328 auto& handler = eventHandlers[key.Value()];
329 if (handler) {
330 (handler)(this);
331 return true;
332 }
333 return false;
334 }
335
SupportsAction(const ActionKey & key)336 bool Window::SupportsAction(const ActionKey& key)
337 {
338 return eventHandlers[key.Value()];
339 }
340
DispatchMouseMotion(View * target,const MouseEvent & me)341 void Window::DispatchMouseMotion(View* target, const MouseEvent& me)
342 {
343 if (hoverView && target != hoverView) {
344 hoverView->MouseLeave(me, drag.get());
345 }
346
347 if (target && target != hoverView) {
348 // must create the drag event before calling MouseEnter
349 target->MouseEnter(me, drag.get());
350 }
351
352 if (trackingView && Distance(dragOrigin, me.Pos()) > EventMgr::mouseDragRadius) {
353 // tracking will eat this event
354 if (me.buttonStates) {
355 trackingView->MouseDrag(me);
356 if (trackingView == target && drag == nullptr) {
357 drag = trackingView->DragOperation();
358 }
359 } else {
360 trackingView = NULL;
361 }
362 } else if (target) {
363 target->MouseOver(me);
364 }
365 hoverView = target;
366 }
367
DispatchMouseDown(View * target,const MouseEvent & me,unsigned short mod)368 void Window::DispatchMouseDown(View* target, const MouseEvent& me, unsigned short mod)
369 {
370 assert(target);
371
372 if (me.button == GEM_MB_ACTION
373 && !(Flags() & View::IgnoreEvents)
374 ) {
375 Focus();
376 }
377
378 TrySetFocus(target);
379 target->MouseDown(me, mod);
380 trackingView = target; // all views track the mouse within their bounds
381 dragOrigin = me.Pos();
382 assert(me.buttonStates);
383 }
384
DispatchMouseUp(View * target,const MouseEvent & me,unsigned short mod)385 void Window::DispatchMouseUp(View* target, const MouseEvent& me, unsigned short mod)
386 {
387 assert(target);
388
389 if (drag && drag->dragView != target && target->AcceptsDragOperation(*drag)) {
390 drag->dropView = target;
391 target->CompleteDragOperation(*drag);
392 } else if (trackingView) {
393 if (trackingView == target || trackingView->TracksMouseDown()) {
394 trackingView->MouseUp(me, mod);
395 }
396 } else if (target) {
397 target->MouseUp(me, mod);
398 }
399 drag = NULL;
400 trackingView = NULL;
401 }
402
DispatchTouchDown(View * target,const TouchEvent & te,unsigned short mod)403 void Window::DispatchTouchDown(View* target, const TouchEvent& te, unsigned short mod)
404 {
405 assert(target);
406
407 if (te.numFingers == 1
408 && !(Flags() & View::IgnoreEvents)) {
409 Focus();
410 }
411
412 TrySetFocus(target);
413 target->TouchDown(te, mod);
414 trackingView = target; // all views track the mouse within their bounds
415 }
416
DispatchTouchUp(View * target,const TouchEvent & te,unsigned short mod)417 void Window::DispatchTouchUp(View* target, const TouchEvent& te, unsigned short mod)
418 {
419 assert(target);
420
421 if (drag && te.numFingers == 1) {
422 if (target->AcceptsDragOperation(*drag) && drag->dragView != target) {
423 drag->dropView = target;
424 target->CompleteDragOperation(*drag);
425 }
426 } else if (trackingView) {
427 if (trackingView == target || trackingView->TracksMouseDown())
428 trackingView->TouchUp(te, mod);
429 } else if (target) {
430 target->TouchUp(te, mod);
431 }
432 drag = NULL;
433 trackingView = NULL;
434 }
435
DispatchTouchGesture(View * target,const GestureEvent & gesture)436 void Window::DispatchTouchGesture(View* target, const GestureEvent& gesture)
437 {
438 // FIXME: this is incomplete
439 // this should be a bit closer to DispatchMouseMotion
440 // drag and drop for example wont function
441
442 //trackingView = target;
443 target->TouchGesture(gesture);
444 }
445
DispatchKey(View * keyView,const Event & event)446 bool Window::DispatchKey(View* keyView, const Event& event)
447 {
448 // hotkeys first
449 std::map<KeyboardKey, EventMgr::EventCallback>::iterator it = HotKeys.find(event.keyboard.keycode);
450 if (it != HotKeys.end()) {
451 return (it->second)(event);
452 }
453
454 // try the keyView view first, if it fails have the window itself try
455 bool handled = false;
456 if (keyView) {
457 handled = (event.type == Event::KeyDown)
458 ? keyView->KeyPress(event.keyboard, event.mod)
459 : keyView->KeyRelease(event.keyboard, event.mod);
460 }
461
462 if (!handled) {
463 // FIXME: using OnKeyPress to avoid eventProxy from eating esc key
464 // would be better if we could delegate proxies for different event classes (keys, mouse, etc)
465 handled = (event.type == Event::KeyDown)
466 ? OnKeyPress(event.keyboard, event.mod)
467 : OnKeyRelease(event.keyboard, event.mod);
468 }
469 return handled;
470 }
471
DispatchEvent(const Event & event)472 bool Window::DispatchEvent(const Event& event)
473 {
474 View* target = NULL;
475
476 if (event.type == Event::TextInput) {
477 focusView->TextInput(event.text);
478 return true;
479 }
480
481 if (!event.isScreen) { // key events
482 return DispatchKey(focusView, event);
483 }
484
485 if (event.type == Event::TouchGesture) {
486 if (trackingView) {
487 DispatchTouchGesture(trackingView, event.gesture);
488 }
489 return true;
490 }
491
492 Point screenPos = event.mouse.Pos();
493 if (!frame.PointInside(screenPos) && trackingView == nullptr) {
494 // this can hapen if the window is modal since it will absorb all events
495 // the window manager maybe shouldnt dispatch the events in this case
496 // but this is a public function and its possible to post a phoney event from anywhere anyway
497 return true;
498 }
499
500 target = SubviewAt(ConvertPointFromScreen(screenPos), false, true);
501 assert(target == nullptr || target->IsVisible());
502
503 if (IsDragable() && target == nullptr) {
504 target = this;
505 }
506
507 // special event handling
508 switch (event.type) {
509 case Event::MouseScroll:
510 // retarget if NULL or disabled
511 {
512 Point delta = event.mouse.Delta();
513 if (target == nullptr || target->IsDisabled()) {
514 target = this;
515 }
516
517 target->MouseWheelScroll(delta);
518 return true;
519 }
520 case Event::MouseMove:
521 // allows NULL and disabled targets
522 if (target == this) {
523 // skip the usual dispatch
524 // this is so that we can move windows that otherwise ignore events
525 OnMouseDrag(event.mouse);
526 } else {
527 DispatchMouseMotion(target, event.mouse);
528 }
529 return true;
530 default:
531 if (target == NULL) {
532 target = this;
533 } else if (target->IsDisabled()) {
534 return true; // we still absorb the event
535 }
536 break;
537 }
538
539 assert(target);
540 // basic event handling
541 switch (event.type) {
542 case Event::MouseDown:
543 DispatchMouseDown(target, event.mouse, event.mod);
544 break;
545 case Event::MouseUp:
546 DispatchMouseUp(target, event.mouse, event.mod);
547 break;
548 case Event::TouchDown:
549 DispatchTouchDown(target, event.touch, event.mod);
550 break;
551 case Event::TouchUp:
552 DispatchTouchUp(target, event.touch, event.mod);
553 break;
554 default:
555 assert(false); // others should be handled above
556 }
557 // absorb other screen events i guess
558 return true;
559 }
560
InActionHandler() const561 bool Window::InActionHandler() const
562 {
563 for (std::set<Control *>::iterator c = Controls.begin(); c != Controls.end(); ++c) {
564 Control* ctrl = *c;
565 if (ctrl->IsExecutingResponseHandler()) {
566 return true;
567 }
568 }
569
570 return executingResponseHandler;
571 }
572
RegisterHotKeyCallback(EventMgr::EventCallback cb,KeyboardKey key)573 bool Window::RegisterHotKeyCallback(EventMgr::EventCallback cb, KeyboardKey key)
574 {
575 if (key < ' ') { // allowing certain non printables (eg 'F' keys)
576 return false;
577 }
578
579 std::map<KeyboardKey, EventMgr::EventCallback>::iterator it;
580 it = HotKeys.find(key);
581 if (it != HotKeys.end()) {
582 // something already registered
583 HotKeys.erase(it);
584 }
585
586 HotKeys[key] = cb;
587 return true;
588 }
589
UnRegisterHotKeyCallback(EventMgr::EventCallback cb,KeyboardKey key)590 bool Window::UnRegisterHotKeyCallback(EventMgr::EventCallback cb, KeyboardKey key)
591 {
592 KeyMap::iterator it = HotKeys.find(key);
593 if (it != HotKeys.end() && FunctionTargetsEqual(it->second, cb)) {
594 HotKeys.erase(it);
595 return true;
596 }
597 return false;
598 }
599
OnMouseDrag(const MouseEvent & me)600 bool Window::OnMouseDrag(const MouseEvent& me)
601 {
602 assert(me.buttonStates);
603 // dragging the window to a new position. only happens with left mouse.
604 if (IsDragable()) {
605 Point newOrigin = frame.Origin() - me.Delta();
606 SetFrameOrigin(newOrigin);
607 } else {
608 ScrollView::OnMouseDrag(me);
609 }
610 return true;
611 }
612
OnMouseLeave(const MouseEvent & me,const DragOp *)613 void Window::OnMouseLeave(const MouseEvent& me, const DragOp*)
614 {
615 DispatchMouseMotion(NULL, me);
616 }
617
OnKeyPress(const KeyboardEvent & key,unsigned short mod)618 bool Window::OnKeyPress(const KeyboardEvent& key, unsigned short mod)
619 {
620 if (Flags() & View::IgnoreEvents) {
621 return false;
622 }
623 switch (key.keycode) {
624 case GEM_ESCAPE:
625 Close();
626 return true;
627 }
628 return ScrollView::OnKeyPress(key, mod);
629 }
630
OnControllerButtonDown(const ControllerEvent & ce)631 bool Window::OnControllerButtonDown(const ControllerEvent& ce)
632 {
633 if (ce.button == CONTROLLER_BUTTON_BACK) {
634 Close();
635 return true;
636 }
637
638 return View::OnControllerButtonDown(ce);
639 }
640
CreateScriptingRef(ScriptingId id,ResRef group)641 ViewScriptingRef* Window::CreateScriptingRef(ScriptingId id, ResRef group)
642 {
643 return new WindowScriptingRef(this, id, group);
644 }
645
646 }
647