1 /* GemRB - Infinity Engine Emulator
2  * Copyright (C) 2015 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 #include "WindowManager.h"
20 
21 #include "GameData.h"
22 #include "Interface.h"
23 #include "ImageMgr.h"
24 #include "Window.h"
25 
26 #include "defsounds.h"
27 
28 #define WIN_IT(w) \
29 std::find(windows.begin(), windows.end(), w)
30 
31 namespace GemRB {
32 
33 int WindowManager::ToolTipDelay = 500;
34 unsigned long WindowManager::TooltipTime = 0;
35 
36 Holder<Sprite2D> WindowManager::CursorMouseUp;
37 Holder<Sprite2D> WindowManager::CursorMouseDown;
38 
SetTooltipDelay(int delay)39 void WindowManager::SetTooltipDelay(int delay)
40 {
41 	ToolTipDelay = delay;
42 }
43 
WindowManager(Video * vid)44 WindowManager::WindowManager(Video* vid)
45 : tooltip(core->CreateTooltip())
46 {
47 	assert(vid);
48 
49 	cursorFeedback = MOUSE_ALL;
50 
51 	hoverWin = NULL;
52 	modalWin = NULL;
53 	trackingWin = NULL;
54 
55 	EventMgr::EventCallback cb = METHOD_CALLBACK(&WindowManager::DispatchEvent, this);
56 	eventMgr.RegisterEventMonitor(cb);
57 
58 	cb = METHOD_CALLBACK(&WindowManager::HotKey, this);
59 	eventMgr.RegisterHotKeyCallback(cb, 'f', GEM_MOD_CTRL);
60 	eventMgr.RegisterHotKeyCallback(cb, GEM_GRAB, 0);
61 
62 	screen = Region(Point(), vid->GetScreenSize());
63 	// FIXME: technically we should unset the current video event manager...
64 	vid->SetEventMgr(&eventMgr);
65 
66 	gameWin = new Window(screen, *this);
67 	gameWin->SetFlags(Window::Borderless|View::Invisible, OP_SET);
68 	gameWin->SetFrame(screen);
69 
70 	HUDBuf = vid->CreateBuffer(screen, Video::BufferFormat::DISPLAY_ALPHA);
71 
72 	// set the buffer that always gets cleared just in case anything
73 	// tries to draw
74 	vid->PushDrawingBuffer(HUDBuf);
75 	video = vid;
76 	// TODO: changing screen size should adjust window positions too
77 	// TODO: how do we get notified if the Video driver changes size?
78 }
79 
~WindowManager()80 WindowManager::~WindowManager()
81 {
82 	DestroyWindows(closedWindows);
83 	assert(closedWindows.empty());
84 	DestroyWindows(windows);
85 	assert(windows.empty());
86 
87 	video.release();
88 	delete gameWin;
89 }
90 
DestroyWindows(WindowList & list)91 void WindowManager::DestroyWindows(WindowList& list)
92 {
93 	WindowList::iterator it = list.begin();
94 	for (; it != list.end();) {
95 		Window* win = *it;
96 		// IMPORTANT: ensure the window (a control subview) isnt executing a callback before deleting it
97 		if (win->InActionHandler() == false) {
98 			delete win;
99 			it = list.erase(it);
100 		} else {
101 			++it;
102 		}
103 	}
104 }
105 
IsOpenWindow(Window * win) const106 bool WindowManager::IsOpenWindow(Window* win) const
107 {
108 	WindowList::const_iterator it = WIN_IT(win);
109 	return it != windows.end();
110 }
111 
IsPresentingModalWindow() const112 bool WindowManager::IsPresentingModalWindow() const
113 {
114 	return modalWin != NULL;
115 }
116 
117 /** Show a Window in Modal Mode */
PresentModalWindow(Window * win,ModalShadow shadow)118 bool WindowManager::PresentModalWindow(Window* win, ModalShadow shadow)
119 {
120 	if (!IsOpenWindow( win )) return false;
121 
122 	OrderFront(win);
123 	win->SetDisabled(false);
124 	win->SetFlags(Window::Modal, OP_OR);
125 	modalWin = win;
126 	modalShadow = shadow;
127 
128 	if (win->Flags() & Window::Borderless && !(win->Flags() & Window::NoSounds)) {
129 		core->PlaySound(DS_WINDOW_OPEN, SFX_CHAN_GUI);
130 	}
131 
132 	return true;
133 }
134 
SetCursorFeedback(CursorFeedback feedback)135 WindowManager::CursorFeedback WindowManager::SetCursorFeedback(CursorFeedback feedback)
136 {
137 	std::swap(feedback, cursorFeedback);
138 	return feedback;
139 }
140 
141 /** Sets a Window on the Top */
FocusWindow(Window * win)142 bool WindowManager::FocusWindow(Window* win)
143 {
144 	if (!IsPresentingModalWindow() && OrderFront(win)) {
145 		if (gameWin == win) {
146 			core->SetEventFlag(EF_CONTROL);
147 		}
148 
149 		return !win->IsDisabled();
150 	}
151 	return false;
152 }
153 
OrderFront(Window * win)154 bool WindowManager::OrderFront(Window* win)
155 {
156 	assert(windows.size()); // win should be contained in windows
157 	win->SetVisible(true);
158 	return OrderRelativeTo(win, windows.front(), true);
159 }
160 
OrderBack(Window * win)161 bool WindowManager::OrderBack(Window* win)
162 {
163 	assert(windows.size()); // win should be contained in windows
164 	return OrderRelativeTo(win, windows.back(), false);
165 }
166 
OrderRelativeTo(Window * win,Window * win2,bool front)167 bool WindowManager::OrderRelativeTo(Window* win, Window* win2, bool front)
168 {
169 	if (win == NULL || win == win2) {
170 		return false;
171 	}
172 	// FIXME: this should probably account for modal windows
173 	// shouldnt beable to move non modals in front of modals, nor one modal to infront of another
174 
175 	Window* oldFront = windows.front();
176 	// if we only have one window, or the 2 windows are the same it is an automatic success
177 	if (windows.size() > 1 && win != win2) {
178 		WindowList::iterator it = WIN_IT(win), it2 = WIN_IT(win2);
179 		if (it == windows.end() || it2 == windows.end()) return false;
180 
181 		windows.erase(it);
182 		// it2 may have become invalid after erase
183 		it2 = WIN_IT(win2);
184 		windows.insert((front) ? it2 : ++it2, win);
185 	}
186 
187 	Window* frontWin = windows.front();
188 	if ((front && frontWin == win2) || win == frontWin) {
189 		TooltipTime = 0;
190 	}
191 
192 	if (oldFront != frontWin) {
193 		if (front) {
194 			if (trackingWin == win2) {
195 				trackingWin = nullptr;
196 			}
197 		} else {
198 			if (trackingWin == win) {
199 				trackingWin = nullptr;
200 			}
201 		}
202 
203 		auto event = eventMgr.CreateMouseMotionEvent(eventMgr.MousePos());
204 		WindowList::const_iterator it = windows.begin();
205 		hoverWin = NextEventWindow(event, it);
206 
207 		oldFront->FocusLost();
208 		frontWin->FocusGained();
209 	}
210 
211 	return true;
212 }
213 
MakeWindow(const Region & rgn)214 Window* WindowManager::MakeWindow(const Region& rgn)
215 {
216 	DestroyWindows(closedWindows);
217 
218 	Window* win = new Window(rgn, *this);
219 	windows.push_back(win);
220 	return win;
221 }
222 
223 //this function won't delete the window, just queue it for deletion
224 //it will be deleted when another window is opened
225 //regardless, the window deleted is inaccessible for gui scripts and
226 //other high level functions from now
227 // this is a caching mechanisim in case the window is reopened
CloseWindow(Window * win)228 void WindowManager::CloseWindow(Window* win)
229 {
230 	if (win == nullptr || win == gameWin) {
231 		return;
232 	}
233 
234 	WindowList::iterator it = WIN_IT(win);
235 	if (it == windows.end()) return;
236 
237 	if (win == modalWin) {
238 		if (win->Flags() & Window::Borderless && !(win->Flags() & Window::NoSounds)) {
239 			core->PlaySound(DS_WINDOW_CLOSE, SFX_CHAN_GUI);
240 		}
241 
242 		WindowList::iterator mit = it;
243 		win->SetFlags(Window::Modal, OP_NAND);
244 		// find the next modal window
245 		modalWin = NULL;
246 		while (++mit != windows.end()) {
247 			if ((*mit)->Flags() & Window::Modal) {
248 				modalWin = *mit;
249 				modalWin->FocusGained();
250 				break;
251 			}
252 		}
253 	}
254 
255 	if (win == hoverWin) {
256 		hoverWin = NULL;
257 	}
258 
259 	if (win == trackingWin) {
260 		trackingWin = NULL;
261 	}
262 
263 	bool isFront = it == windows.begin();
264 	it = windows.erase(it);
265 	if (it != windows.end()) {
266 		Window* newFrontWin = *it;
267 		// the window beneath this must get redrawn
268 		newFrontWin->MarkDirty();
269 		if (isFront && newFrontWin->IsVisible()) {
270 			newFrontWin->Focus();
271 			// normally Focus() will call FocusGained(), but it
272 			// this case we must do it manually because we have erased from windows so Focus() will think we already have focus
273 			newFrontWin->FocusGained();
274 		}
275 	}
276 	closedWindows.push_back(win);
277 
278 	win->SetVisible(false);
279 	win->SetDisabled(true);
280 }
281 
DestroyAllWindows()282 void WindowManager::DestroyAllWindows()
283 {
284 	WindowList::iterator it = windows.begin();
285 	for (; it != windows.end(); ++it) {
286 		Window* win = *it;
287 		win->SetFlags(Window::DestroyOnClose, OP_OR); // force delete
288 		win->Close();
289 		if (windows.empty()) break;
290 	}
291 }
292 
HotKey(const Event & event)293 bool WindowManager::HotKey(const Event& event)
294 {
295 	if (event.type == Event::KeyDown && event.keyboard.repeats == 1) {
296 		switch (event.keyboard.keycode) {
297 			case 'f':
298 				video->ToggleFullscreenMode();
299 				return true;
300 			case GEM_GRAB:
301 				video->ToggleGrabInput();
302 				return true;
303 			default:
304 				return false;
305 		}
306 	}
307 	return false;
308 }
309 
GetFocusWindow() const310 Window* WindowManager::GetFocusWindow() const
311 {
312 	if (IsPresentingModalWindow()) {
313 		return modalWin;
314 	}
315 
316 	for (Window* win : windows) {
317 		if ((win->Flags() & (View::IgnoreEvents | View::Invisible)) == 0) {
318 			return win;
319 		}
320 	}
321 
322 	// for all intents and purposes there must always be a window considered to be the focus
323 	// gameWin is the "root" window so it will be considered the focus if no eligable windows are
324 	return gameWin;
325 }
326 
327 #define HIT_TEST(e, w) \
328 ((w)->HitTest((w)->ConvertPointFromScreen(e.mouse.Pos())))
329 
NextEventWindow(const Event & event,WindowList::const_iterator & current)330 Window* WindowManager::NextEventWindow(const Event& event, WindowList::const_iterator& current)
331 {
332 	if (current == windows.end()) {
333 		// we already we through them all and returned gameWin or modalWin once. there is no target window after gameWin
334 		return NULL;
335 	}
336 
337 	if (IsPresentingModalWindow()) {
338 		// modal win is always the target for all events no matter what
339 		// if the window shouldnt handle sreen events outside its bounds (ie negative coords etc)
340 		// then the Window class should be responsible for bounds checking
341 
342 		// the NULL return is so that if this is called again after returning modalWindow there is no NextTarget
343 		current = windows.end(); // invalidate the iterator, no other target is possible.
344 		return modalWin;
345 	}
346 
347 	if (event.isScreen) {
348 		while (current != windows.end()) {
349 			Window* win = *current++;
350 			if (win->IsVisible() && HIT_TEST(event,win)) {
351 				// NOTE: we want to "target" the first window hit regardless of it being disabled or otherwise
352 				// we still need to update which window is under the mouse and block events from reaching the windows below
353 				return win;
354 			}
355 		}
356 	} else {
357 		current = windows.end(); // invalidate the iterator, no other target is possible.
358 		return GetFocusWindow();
359 	}
360 
361 	// we made it though with no takers...
362 	// send it to the game win
363 	return gameWin;
364 }
365 
DispatchEvent(const Event & event)366 bool WindowManager::DispatchEvent(const Event& event)
367 {
368 	if (eventMgr.MouseDown() == false && eventMgr.FingerDown() == false) {
369 		if (event.type == Event::MouseUp || event.type == Event::TouchUp) {
370 			if (trackingWin) {
371 				if (trackingWin->IsDisabled() == false) {
372 					trackingWin->DispatchEvent(event);
373 				}
374 
375 				trackingWin = NULL;
376 			}
377 
378 			// we don't deliver mouse up events if there isn't a corresponding mouse down (trackingWin == NULL).
379 			return false;
380 		}
381 
382 		if (event.type != Event::TouchGesture) {
383 			trackingWin = NULL;
384 		}
385 	} else if (event.isScreen && trackingWin) {
386 			if (trackingWin->IsDisabled() == false) {
387 				trackingWin->DispatchEvent(event);
388 			}
389 			return true;
390 		}
391 
392 	if (windows.empty()) return false;
393 
394 	if (event.EventMaskFromType(event.type) & Event::AllMouseMask) {
395 		TooltipTime = GetTicks();
396 
397 		// handle when mouse leaves the window
398 		if (hoverWin && HIT_TEST(event, hoverWin) == false) {
399 			hoverWin->MouseLeave(event.mouse, NULL);
400 			hoverWin = NULL;
401 		}
402 	// handled here instead of as a hotkey, so also gamecontrol can do its thing
403 	} else if (event.type == Event::KeyDown && event.keyboard.keycode == GEM_TAB) {
404 		if (TooltipTime + ToolTipDelay > GetTicks()) {
405 			TooltipTime -= ToolTipDelay;
406 		}
407 	}
408 
409 	WindowList::const_iterator it = windows.begin();
410 	while (Window* target = NextEventWindow(event, it)) {
411 		// disabled windows get no events, but should block them from going to windows below
412 		if (target->IsDisabled() || target->DispatchEvent(event)) {
413 			if (event.isScreen && target->IsVisible()) {
414 				hoverWin = target;
415 				if (event.type == Event::MouseDown || event.type == Event::TouchDown) {
416 					trackingWin = target;
417 				}
418 			} else if ((target->Flags()&(View::IgnoreEvents|View::Disabled)) == View::Disabled
419 					   && event.type == Event::KeyDown && event.keyboard.keycode == GEM_ESCAPE) {
420 				// force close disabled windows if they arent also ignoreing events
421 				target->Close();
422 			}
423 			return true;
424 		}
425 	}
426 
427 	return false;
428 }
429 
430 #undef HIT_TEST
431 
DrawMouse() const432 void WindowManager::DrawMouse() const
433 {
434 	if (cursorFeedback == MOUSE_NONE)
435 		return;
436 
437 	Point pos = eventMgr.MousePos();
438 	DrawCursor(pos);
439 	DrawTooltip(pos);
440 }
441 
DrawCursor(const Point & pos) const442 void WindowManager::DrawCursor(const Point& pos) const
443 {
444 	if (cursorFeedback&MOUSE_NO_CURSOR) {
445 		return;
446 	}
447 	// Cursor draw priority:
448 	// 1. gamewin cursor trumps all (we use this for drag items and some others)
449 	// 2. hoverwin cursor
450 	// 3. hoverview cursor
451 	// 4. WindowManager cursors
452 
453 	Holder<Sprite2D> cur(gameWin->View::Cursor());
454 
455 	if (!cur && hoverWin) {
456 		cur = hoverWin->Cursor();
457 	}
458 
459 	if (!cur) {
460 		// no cursor override
461 		cur = (eventMgr.MouseDown()) ? CursorMouseDown : CursorMouseUp;
462 	}
463 	assert(cur); // must have a cursor
464 
465 	if (hoverWin && hoverWin->IsDisabledCursor()) {
466 		// draw greayed cursor
467 		video->BlitGameSprite(cur, pos, BlitFlags::GREY|BlitFlags::BLENDED, ColorGray);
468 	} else {
469 		// draw normal cursor
470 		video->BlitSprite(cur, pos);
471 	}
472 }
473 
DrawTooltip(Point pos) const474 void WindowManager::DrawTooltip(Point pos) const
475 {
476 	if (cursorFeedback&MOUSE_NO_TOOLTIPS) {
477 		return;
478 	}
479 
480 	if (trackingWin) // if the mouse is held down we dont want tooltips
481 		TooltipTime = GetTicks();
482 
483 	if (tooltip.time != TooltipTime + ToolTipDelay) {
484 		tooltip.time = TooltipTime + ToolTipDelay;
485 		tooltip.reset = true;
486 	}
487 
488 	if (hoverWin && TooltipTime && GetTicks() >= tooltip.time) {
489 		if (tooltip.reset) {
490 			// reset the tooltip and restart the sound
491 			const String& text = hoverWin->TooltipText();
492 			tooltip.tt.SetText(text);
493 			if (tooltip.tooltip_sound) {
494 				tooltip.tooltip_sound->Stop();
495 				tooltip.tooltip_sound.release();
496 			}
497 			if (text.length()) {
498 				tooltip.tooltip_sound = core->PlaySound(DS_TOOLTIP, SFX_CHAN_GUI);
499 			}
500 			tooltip.reset = false;
501 		}
502 
503 		// clamp pos so that the TT is all visible (TT draws centered at pos)
504 		int halfW = tooltip.tt.TextSize().w/2 + 16;
505 		int halfH = tooltip.tt.TextSize().h/2 + 11;
506 		pos.x = Clamp<int>(pos.x, halfW, screen.w - halfW);
507 		pos.y = Clamp<int>(pos.y, halfW, screen.h - halfH);
508 
509 		tooltip.tt.Draw(pos);
510 	}
511 }
512 
DrawWindowFrame(BlitFlags flags) const513 void WindowManager::DrawWindowFrame(BlitFlags flags) const
514 {
515 	// the window buffers dont have room for the frame
516 	// we also only need to draw the frame *once* (even if it applies to multiple windows)
517 	// therefore, draw the frame on its own buffer (above everything else)
518 	// ... I'm not 100% certain this works for all use cases.
519 	// if it doesnt... i think it might be better to just forget about the window frames once the game is loaded
520 
521 	video->SetScreenClip( NULL );
522 
523 	Holder<Sprite2D> left_edge = WinFrameEdge(0);
524 	if (left_edge) {
525 		// we assume if one fails, they all do
526 		Holder<Sprite2D> right_edge = WinFrameEdge(1);
527 		Holder<Sprite2D> top_edge = WinFrameEdge(2);
528 		Holder<Sprite2D> bot_edge = WinFrameEdge(3);
529 
530 		int left_w = left_edge->Frame.w;
531 		int right_w = right_edge->Frame.w;
532 		int v_margin = (screen.h - left_edge->Frame.h) / 2;
533 		// Also assume top and bottom are the same width.
534 		int h_margin = (screen.w - left_w - right_w - top_edge->Frame.w) / 2;
535 
536 		const static Color dummy;
537 		video->BlitGameSprite(left_edge, Point(h_margin, v_margin), flags, dummy);
538 		video->BlitGameSprite(right_edge, Point(screen.w - right_w - h_margin, v_margin), flags, dummy);
539 		video->BlitGameSprite(top_edge, Point(h_margin + left_w, v_margin), flags, dummy);
540 		video->BlitGameSprite(bot_edge, Point(h_margin + left_w, screen.h - bot_edge->Frame.h - v_margin), flags, dummy);
541 	}
542 }
543 
DrawHUD()544 WindowManager::HUDLock WindowManager::DrawHUD()
545 {
546 	return HUDLock(*this);
547 }
548 
DrawWindows() const549 void WindowManager::DrawWindows() const
550 {
551 	HUDBuf->Clear();
552 
553 	if (!windows.size()) {
554 		return;
555 	}
556 
557 	// draw the game window now (beneath everything else); its not part of the windows collection
558 	if (gameWin->IsVisible()) {
559 		gameWin->Draw();
560 	} else {
561 		// something must get drawn or else we get smearing
562 		// this is kind of a hacky way to clear it, but it works
563 		auto& buffer = gameWin->DrawWithoutComposition();
564 		buffer->Clear();
565 		video->PushDrawingBuffer(buffer);
566 	}
567 
568 	bool drawFrame = false;
569 	Window* frontWin = windows.front();
570 	const Region& frontWinFrame = frontWin->Frame();
571 	// we have to draw windows from the bottom up so the front window is drawn last
572 	WindowList::const_reverse_iterator rit = windows.rbegin();
573 	for (; rit != windows.rend(); ++rit) {
574 		Window* win = *rit;
575 
576 		if (!win->IsVisible())
577 			continue;
578 
579 		if (win == modalWin) {
580 			drawFrame = drawFrame || !(win->Flags()&Window::Borderless);
581 			continue; // will draw this later
582 		}
583 
584 		const Region& frame = win->Frame();
585 
586 		// FYI... this only checks if the front window obscures... could be covered by another window too
587 		if ((frontWin->Flags()&(Window::AlphaChannel|View::Invisible)) == 0 && win != frontWin && win->NeedsDraw()) {
588 			Region intersect = frontWinFrame.Intersect(frame);
589 			if (intersect == frame) {
590 				// this window is completely obscured by the front window
591 				// we dont have to bother drawing it because IE has no concept of translucent windows
592 				continue;
593 			}
594 		}
595 
596 		if (!drawFrame && !(win->Flags()&Window::Borderless) && (frame.w < screen.w || frame.h < screen.h)) {
597 			// the window requires us to draw the frame border (happens later, on the cursor buffer)
598 			drawFrame = true;
599 		}
600 
601 		if (win->IsDisabled() && win->NeedsDraw()) {
602 			// Important to only draw if the window itself is dirty
603 			// controls on greyed out windows shouldnt be updating anyway
604 			win->Draw();
605 			Region winrgn(Point(), win->Dimensions());
606 			video->DrawRect(winrgn, ColorBlack, true, BlitFlags::HALFTRANS|BlitFlags::BLENDED);
607 		} else {
608 			win->Draw();
609 		}
610 	}
611 
612 	video->PushDrawingBuffer(HUDBuf);
613 
614 	BlitFlags frame_flags = BlitFlags::NONE;
615 	if (modalWin) {
616 		if (modalShadow != ShadowNone) {
617 			if (modalShadow == ShadowGray) {
618 				frame_flags |= BlitFlags::HALFTRANS;
619 			}
620 			video->DrawRect(screen, ColorBlack, true, frame_flags);
621 		}
622 		auto& modalBuffer = modalWin->DrawWithoutComposition();
623 		video->BlitVideoBuffer(modalBuffer, Point(), BlitFlags::BLENDED);
624 	}
625 
626 	if (drawFrame) {
627 		DrawWindowFrame(frame_flags);
628 	}
629 
630 	if (core->InDebugMode(ID_WINDOWS)) {
631 		// ensure this is drawin over the window frames
632 		if (trackingWin) {
633 			Region r = trackingWin->Frame();
634 			r.ExpandAllSides(5);
635 			video->DrawRect(r, ColorRed, false);
636 		}
637 
638 		if (hoverWin) {
639 			Region r = hoverWin->Frame();
640 			r.ExpandAllSides(10);
641 			video->DrawRect(r, ColorWhite, false);
642 		}
643 	}
644 
645 	if (!modalWin && !drawFrame && FadeColor.a > 0) {
646 		video->DrawRect(screen, FadeColor, true);
647 	}
648 
649 	DrawMouse();
650 
651 	// Be sure to reset this to nothing, else some renderer backends (metal at least) complain when we clear (swapbuffers)
652 	video->SetScreenClip(NULL);
653 }
654 
655 //copies a screenshot into a sprite
GetScreenshot(Window * win)656 Holder<Sprite2D> WindowManager::GetScreenshot(Window* win)
657 {
658 	Holder<Sprite2D> screenshot;
659 	if (win) { // we dont really care if we are managing the window
660 		// only a screen shot of passed win
661 		auto& winBuf = win->DrawWithoutComposition();
662 		screenshot = video->GetScreenshot( Region(Point(), win->Dimensions()), winBuf );
663 	} else {
664 		// redraw the windows without the mouse elements
665 		auto mouseState = SetCursorFeedback(MOUSE_NONE);
666 		DrawWindows();
667 		video->SwapBuffers(0);
668 		screenshot = video->GetScreenshot( screen );
669 		SetCursorFeedback(mouseState);
670 	}
671 
672 	return screenshot;
673 }
674 
WinFrameEdge(int edge) const675 Holder<Sprite2D> WindowManager::WinFrameEdge(int edge) const
676 {
677 	std::string refstr = "STON";
678 
679 	// we probably need a HasResource("CSTON" + width + height) call
680 	// to check for a custom resource
681 
682 	if (screen.w >= 800 && screen.w < 1024)
683 		refstr += "08";
684 	else if (screen.w >= 1024)
685 		refstr += "10";
686 
687 	switch (edge) {
688 		case 0:
689 			refstr += "L";
690 			break;
691 		case 1:
692 			refstr += "R";
693 			break;
694 		case 2:
695 			refstr += "T";
696 			break;
697 		case 3:
698 			refstr += "B";
699 			break;
700 	}
701 
702 	ResRef ref = refstr.c_str();
703 	Holder<Sprite2D> frame;
704 	if (winframes.find(ref) != winframes.end()) {
705 		frame = winframes[ref];
706 	} else {
707 		ResourceHolder<ImageMgr> im = GetResourceHolder<ImageMgr>(ref);
708 		if (im) {
709 			frame = im->GetSprite2D();
710 		}
711 		winframes.insert(std::make_pair(ref, frame));
712 	}
713 
714 	return frame;
715 }
716 
717 #undef WIN_IT
718 
719 }
720