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