Leviathan  0.8.0.0
Leviathan game engine
Window.cpp
Go to the documentation of this file.
1 // ------------------------------------ //
2 #include "Window.h"
3 
4 #include "Engine.h"
5 #include "Entities/GameWorld.h"
6 #include "Exceptions.h"
7 #include "FileSystem.h"
8 #include "GUI/GuiLayer.h"
9 #include "GUI/GuiManager.h"
10 #include "Handlers/IDFactory.h"
11 #include "Input/InputController.h"
12 #include "Input/Key.h"
14 #include "Rendering/Graphics.h"
16 #include "TimeIncludes.h"
17 #include "Utility/Convert.h"
18 
19 #include "include/cef_browser.h"
20 #include "include/internal/cef_types.h"
21 #include "include/internal/cef_types_wrappers.h"
22 
23 #ifdef __linux
24 #include "XLibInclude.h"
25 #endif
26 
27 #include "Components/BsCCamera.h"
28 #include "Scene/BsSceneObject.h"
29 
30 #include <SDL.h>
31 #include <SDL_syswm.h>
32 
33 #include <algorithm>
34 #include <thread>
35 // ------------------------------------ //
36 
37 namespace Leviathan {
38 
40 public:
41  // A window always needs to have some camera rendering to it to make the GUI renderer work
42  bs::HSceneObject WindowSceneObject;
43  bs::HCamera WindowCamera;
44 };
45 
46 
47 Window* Window::InputCapturer = nullptr;
48 
49 std::atomic<int> Window::OpenWindowCount = 0;
50 std::atomic<int> Window::TotalCreatedWindows = 0;
51 
52 // ------------------------------------ //
53 DLLEXPORT Window::Window(Graphics* windowcreater, AppDef* windowproperties) :
54  CurrentCursor(nullptr, nullptr), CurrentCursorImage(nullptr, nullptr),
55  ID(IDFactory::GetID())
56 {
57  // Create window //
58 
59  const WindowDataDetails& WData = windowproperties->GetWindowDetails();
60 
61  // TODO: FSAA (if MSAA is disabled)
62 
63  // // set some rendering specific parameters //
64  // Ogre::NameValuePairList WParams;
65 
66  // // variables //
67  // Ogre::String fsaastr = Convert::ToString(WData.FSAA);
68 
69  // // Context needs to be reused for multiple windows
70  // if(OpenWindowCount > 0) {
71 
72  // LOG_INFO("Window: using existing GLX context for creating");
73 
74  // WParams["currentGLContext"] = "true";
75 
76  // // Needs to be forced off to not cause issues like vsyncing each window separately
77  // and
78  // // dropping to "monitor refresh rate / window count" fps
79  // WParams["vsync"] = "false";
80 
81  // } else {
82  // WParams["vsync"] = WData.VSync ? "true" : "false";
83  // }
84 
85  // WParams["FSAA"] = fsaastr;
86  // WParams["gamma"] = WData.UseGamma ? "true" : "false";
87 
88  // Ogre::String wcaption = WData.Title;
89 
90  int extraFlags = 0;
91 
92  if(WData.FullScreen == "no" || WData.FullScreen == "0") {
93  } else if(WData.FullScreen == "fullscreendesktop") {
94  extraFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
95  } else if(WData.FullScreen == "fullscreenvideomode") {
96  extraFlags |= SDL_WINDOW_FULLSCREEN;
97  } else {
98 
99  LOG_ERROR("Window: invalid fullscreen value: " + WData.FullScreen);
100  }
101 
102  // TODO: On Apple's OS X you must set the NSHighResolutionCapable
103  // Info.plist property to YES, otherwise you will not receive a
104  // High DPI OpenGL
105  // canvas. https://wiki.libsdl.org/SDL_CreateWindow
106 
107  SDLWindow = SDL_CreateWindow(WData.Title.c_str(),
108  SDL_WINDOWPOS_UNDEFINED_DISPLAY(WData.DisplayNumber),
109  SDL_WINDOWPOS_UNDEFINED_DISPLAY(WData.DisplayNumber), WData.Width, WData.Height,
110  // This seems to cause issues on Windows
111  // SDL_WINDOW_OPENGL |
112  SDL_WINDOW_RESIZABLE | extraFlags);
113 
114  // SDL_WINDOW_FULLSCREEN_DESKTOP works so much better than
115  // SDL_WINDOW_FULLSCREEN so it should be always used
116 
117  // SDL_WINDOW_BORDERLESS
118  // SDL_WINDOWPOS_UNDEFINED_DISPLAY(x)
119  // SDL_WINDOWPOS_CENTERED_DISPLAY(x)
120 
121  if(!SDLWindow) {
122 
123  LOG_FATAL("SDL Window creation failed, error: " + std::string(SDL_GetError()));
124  }
125 
126  // SDL_GLContext glContext = SDL_GL_CreateContext(SDLWindow);
127 
128  SDL_SysWMinfo wmInfo;
129  SDL_VERSION(&wmInfo.version);
130  if(!SDL_GetWindowWMInfo(SDLWindow, &wmInfo)) {
131 
132  LOG_FATAL("Window: created sdl window failed to retrieve info");
133  }
134 
135  BSFWindow = windowcreater->RegisterCreatedWindow(*this);
136 
137  if(!BSFWindow) {
138  throw Exception("Failed to create bsf window");
139  }
140 
141  ++OpenWindowCount;
142 
143  // Store this window's number
144  WindowNumber = ++TotalCreatedWindows;
145 
146 
147 #ifdef _WIN32
148  // Fetch the windows handle from SDL //
149  HWND ourHWND = wmInfo.info.win.window;
150 
151  // apply style settings (mainly ICON) //
152  if(ourHWND) {
153 
154  WData.ApplyIconToHandle(ourHWND);
155 
156  } else {
157  LOG_WARNING("Window: failed to get window HWND for styling");
158  }
159 #else
160  // \todo linux icon
161 #endif
162  // tmpwindow->setDeactivateOnFocusChange(false);
163 
164  // set the new window to be active //
165  // tmpwindow->setActive(true);
166  Focused = true;
167 
168  _BSFResources = std::make_unique<BSFResources>();
169 
170  // Create and attach our camera to the window
171  {
172  _BSFResources->WindowSceneObject = bs::SceneObject::create("window no world camera");
173  _BSFResources->WindowCamera =
174  _BSFResources->WindowSceneObject->addComponent<bs::CCamera>();
175  _BSFResources->WindowCamera->getViewport()->setClearColorValue(Float4(0, 0, 0, 1));
176  _BSFResources->WindowSceneObject->setPosition(Float3(40.0f, 30.0f, 230.0f));
177  _BSFResources->WindowSceneObject->lookAt(Float3(0, 0, 0));
178 
179  _BSFResources->WindowCamera->getViewport()->setTarget(BSFWindow);
180  }
181 
182  // create GUI //
183  WindowsGui = std::make_unique<GUI::GuiManager>();
184  if(!WindowsGui) {
185  // TODO: the window must be destroyed here
186  DEBUG_BREAK;
187  throw NULLPtr("cannot create GUI manager instance");
188  }
189 
190  if(!WindowsGui->Init(windowcreater, this)) {
191 
192  LOG_ERROR("Window: Gui init failed");
193  throw NULLPtr("invalid GUI manager");
194  }
195 
196  // create receiver interface //
197  TertiaryReceiver = std::shared_ptr<InputController>(new InputController());
198 
199  // TODO: this needs to be only used when a text box etc. is used
200  // But that is quite hard to detect
201  SDL_StartTextInput();
202 
203  // cursor on top of window isn't hidden //
204  ApplicationWantCursorState = false;
205 }
206 
208 {
209  _BSFResources.reset();
210 
211  // GUI is very picky about delete order
212  if(WindowsGui) {
213  WindowsGui->Release();
214  WindowsGui.reset();
215  }
216 
217  if(MouseCaptured) {
218 
219  SDL_SetRelativeMouseMode(SDL_FALSE);
220  }
221 
222  // Un fullscreen to make sure nothing is screwed up
223  if(SDL_GetWindowFlags(SDLWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP) {
224 
225  LOG_INFO("Window: unfullscreened before quit");
226  SDL_SetWindowFullscreen(SDLWindow, 0);
227  }
228 
229  // Report that the window is now closed //
230  Logger::Get()->Info(
231  "Window: closing window(" + Convert::ToString(GetWindowNumber()) + ")");
232 
233  // Close the window //
234  // OWindow->destroy();
235 
236  TertiaryReceiver.reset();
237 
238  int windowsafter = --OpenWindowCount;
239 
240  BSFWindow->destroy();
241  BSFWindow.reset();
242 
243  if(windowsafter == 0) {
244 
245  Logger::Get()->Info("Window: all windows have been closed, "
246  "should quit soon");
247  }
248 
249  // LOG_WRITE("TODO: check why calling SDL_DestroyWindow crashes in Ogre "
250  // "GLX plugin uninstall");
251  SDL_DestroyWindow(SDLWindow);
252  // SDL_HideWindow(SDLWindow);
253  SDLWindow = nullptr;
254 }
255 // ------------------------------------ //
256 DLLEXPORT void Window::LinkObjects(std::shared_ptr<GameWorld> world)
257 {
258  if(LinkedWorld == world)
259  return;
260 
261  if(LinkedWorld) {
262 
263  LinkedWorld->OnUnLinkedFromWindow(this, Engine::Get()->GetGraphics());
264  } else {
265  _BSFResources->WindowCamera->getViewport()->setTarget(nullptr);
266  }
267 
268  LinkedWorld = world;
269 
270  if(LinkedWorld) {
271 
272  LinkedWorld->OnLinkToWindow(this, Engine::Get()->GetGraphics());
273  } else {
274 
275  _BSFResources->WindowCamera->getViewport()->setTarget(BSFWindow);
276  }
277 }
278 
280 {
281  LinkObjects(nullptr);
282 }
283 // ------------------------------------ //
284 DLLEXPORT void Window::Tick(int mspassed)
285 {
286  // pass to GUI //
287  WindowsGui->GuiTick(mspassed);
288 }
289 
290 DLLEXPORT bool Window::Render(int mspassed, int tick, int timeintick)
291 {
292  if(LinkedWorld)
293  LinkedWorld->Render(mspassed, tick, timeintick);
294 
295  // Update GUI before each frame //
296  WindowsGui->Render();
297 
298  // BSF does it's own rendering handling
299  return true;
300 }
301 
302 DLLEXPORT void Window::OnResize(int width, int height)
303 {
304  if(DoingResize)
305  return;
306 
307 // Notify bsf
308 // TODO: something better, this is very fiddly
309 #ifdef _WIN32
310  LOG_ERROR("Resizing doesn't work at all on Windows, see: "
311  "https://github.com/GameFoundry/bsf/issues/382");
312 #else
313  BSFWindow->resize(width, height);
314  LOG_WARNING("Resizing is fiddly, see: "
315  "https://github.com/GameFoundry/bsf/issues/382");
316 #endif
317 
318  // These don't work
319  // BSFWindow->_notifyWindowEvent(bs::WindowEventType::Resized);
320  // BSFWindow->_windowMovedOrResized();
321 
322  // Send to GUI //
323  WindowsGui->OnResize();
324 }
325 
327 {
328  if(Focused == focused)
329  return;
330 
331  LOG_INFO("Focus change in Window");
332 
333  // Update mouse //
334  Focused = focused;
336 
337  WindowsGui->OnFocusChanged(focused);
338 
339  if(!Focused && MouseCaptured) {
340 
341  LOG_WRITE("TODO: We need to force GUI on to stop mouse capture");
342  LOG_FATAL("Not implemented unfocus when mouse capture is on");
343  }
344 }
345 // ------------------------------------ //
347 {
348  if(SDLWindow) {
349  SDL_RaiseWindow(SDLWindow);
350  }
351 }
352 
353 // ------------------------------------ //
354 #ifdef __linux
355 DLLEXPORT void Window::SetX11Cursor(int cursor, int retrycount /*= 10*/)
356 {
357  if(retrycount <= 0) {
358 
359  LOG_ERROR("Window: SetX11Cursor: retrycount reached");
360  return;
361  }
362 
364 
365  SDL_SysWMinfo wmInfo;
366  SDL_VERSION(&wmInfo.version);
367  if(!SDL_GetWindowWMInfo(SDLWindow, &wmInfo)) {
368 
369  LOG_ERROR("Window: SetX11Cursor: failed to retrieve wm info");
370  return;
371  }
372 
373  // Retrieve the X11 display shared with Chromium.
374  ::Display* xdisplay = cef_get_xdisplay();
375  if(!xdisplay) {
376 
377  LOG_ERROR("Window: SetX11Cursor: cef_get_xdisplay failed");
378  return;
379  }
380 
381  WantedX11Cursor = cursor;
382  XDefineCursor(xdisplay, wmInfo.info.x11.window, cursor);
383  if(Graphics::HasX11ErrorOccured()) {
384  LOG_ERROR("Window: SetX11Cursor: failed due to x11 error (on define cursor), retrying "
385  "in 50ms");
386 
387  ThreadingManager::Get()->QueueTask(std::make_shared<DelayedTask>(
388  [=]() {
389  Engine::Get()->Invoke([=]() { this->SetX11Cursor(cursor, retrycount - 1); });
390  },
391  MillisecondDuration(50)));
392  return;
393  }
394 
395  // And we need to make it permanent so that the cursor isn't returned to default after it
396  // leaves and re-enters our window
397 
398  // // Todo make this more robust somehow
399  // ThreadingManager::Get()->QueueTask(std::make_shared<DelayedTask>(
400  // [=]() { Engine::Get()->Invoke([=]() { this->MakeX11CursorPermanent(); }); },
401  // MillisecondDuration(10)));
402 
403  MakeX11CursorPermanent();
404 }
405 
406 DLLEXPORT void Window::MakeX11CursorPermanent()
407 {
409 
410  // This needs to be retried if the cursor isn't over our window currently
412 
413  LOG_ERROR("Window: MakeX11CursorPermanent: failed because mouse is outside area, "
414  "retrying SetX11Cursor in 50ms and after mouse is inside");
415 
416  ThreadingManager::Get()->QueueTask(std::make_shared<ConditionalDelayedTask>(
417  [=]() { Engine::Get()->Invoke([=]() { this->SetX11Cursor(WantedX11Cursor); }); },
418  [=]() { return !this->IsMouseOutsideWindowClientArea(); },
419  MillisecondDuration(50)));
420  return;
421  }
422 
423  SDL_SysWMinfo wmInfo;
424  SDL_VERSION(&wmInfo.version);
425  if(!SDL_GetWindowWMInfo(SDLWindow, &wmInfo)) {
426 
427  LOG_ERROR("Window: MakeX11CursorPermanent: failed to retrieve wm info");
428  return;
429  }
430 
431  ::Display* xdisplay = cef_get_xdisplay();
432  if(!xdisplay) {
433 
434  LOG_ERROR("Window: SetX11Cursor: cef_get_xdisplay failed");
435  return;
436  }
437 
438  ::Display* usedDisplay = xdisplay;
439  // ::Display* usedDisplay = wmInfo.info.x11.display;
440 
441  // Verify that we can use XFixes
442  int event_basep;
443  int error_basep;
444  if(!XFixesQueryExtension(usedDisplay, &event_basep, &error_basep)) {
445 
446  LOG_ERROR("Window: MakeX11CursorPermanent: failed due to XFixes extension not being "
447  "available / failing to load, retrying in 50ms");
448  queueretrylabel:
449  ThreadingManager::Get()->QueueTask(std::make_shared<DelayedTask>(
450  [=]() { Engine::Get()->Invoke([=]() { this->MakeX11CursorPermanent(); }); },
451  MillisecondDuration(50)));
452  return;
453  }
454 
455  // Now we can grab the image and feed it to SDL
456  // auto* tmpImg = XFixesGetCursorImage(xdisplay);
457  auto* tmpImg = XFixesGetCursorImage(usedDisplay);
458  if(Graphics::HasX11ErrorOccured()) {
459  LOG_ERROR("Window: MakeX11CursorPermanent: failed due to x11 error "
460  "(XFixesGetCursorImage), retrying in 50ms");
461 
462  goto queueretrylabel;
463  }
464 
465  std::unique_ptr<XFixesCursorImage, int (*)(void*)> xCursor(tmpImg, XFree);
466 
467 
468  // The pixels format is A8R8G8B8
469  std::unique_ptr<SDL_Surface, void (*)(SDL_Surface*)> image(
470  SDL_CreateRGBSurfaceWithFormat(
471  0, xCursor->width, xCursor->height, 32, SDL_PIXELFORMAT_ARGB8888),
472  SDL_FreeSurface);
473 
474  if(!image) {
475 
476  LOG_ERROR("Window: SetX11Cursor: failed to create SDL surface");
477  return;
478  }
479 
480  // We need to copy the data to the SDL pixels and undo alpha premultiply
481  const size_t dataSize = xCursor->width * xCursor->height;
482 
483  // uint8_t* data = reinterpret_cast<uint8_t*>(xCursor->pixels);
484  uint8_t* pixels = static_cast<uint8_t*>(image->pixels);
485 
486  for(size_t i = 0, j = 0; i < dataSize; ++i, j += 4) {
487 
488  // I have no clue why this bit shifting and or works to get the right colour
489  // approach taken from here
490  // https://github.com/landryb/xfce4-screenshooter/blob/master/lib/screenshooter-capture.c
491  const auto fixedColour = (static_cast<uint32_t>(xCursor->pixels[i]) << 8) |
492  (static_cast<uint32_t>(xCursor->pixels[i]) >> 24);
493  // Alpha
494  pixels[j + 0] = fixedColour >> 24;
495  // Red
496  pixels[j + 1] = (fixedColour >> 16) & 0xff;
497  // Green
498  pixels[j + 2] = (fixedColour >> 8) & 0xff;
499  // Blue
500  pixels[j + 3] = fixedColour & 0xff;
501  }
502 
503  std::unique_ptr<SDL_Cursor, void (*)(SDL_Cursor*)> newCursor(
504  SDL_CreateColorCursor(image.get(), xCursor->xhot, xCursor->yhot), SDL_FreeCursor);
505 
506  if(!newCursor) {
507 
508  LOG_ERROR("Window: SetX11Cursor: failed to create sdl cursor");
509  return;
510  }
511 
512  SDL_SetCursor(newCursor.get());
513 
514  // Overwrite the old ones releasing the resources after we are using the new cursor
515  CurrentCursor = std::move(newCursor);
516  CurrentCursorImage = std::move(image);
517 }
518 #endif //__linux
519 
520 #ifdef _WIN32
521 DLLEXPORT void Window::SetWinCursor(HCURSOR cursor)
522 {
523  std::unique_ptr<ICONINFO, void (*)(ICONINFO*)> info(new ICONINFO({0}), [](ICONINFO* icon) {
524  if(icon->hbmColor)
525  DeleteObject(icon->hbmColor);
526 
527  if(icon->hbmMask)
528  DeleteObject(icon->hbmMask);
529  });
530 
531  LEVIATHAN_ASSERT(info.get(), "Memory alloc failed");
532 
533  bool success = GetIconInfo(cursor, info.get());
534 
535  if(!success) {
536 
537  LOG_ERROR("Window: SetWinCursor: failed to get icon info from HCURSOR");
538  return;
539  }
540 
541  BITMAP colourBitmap;
542  BITMAP maskBitmap;
543 
544  int colourBitmapSize = 0;
545  int maskBitmapSize = 0;
546  bool monochrome;
547 
548  // This can be none if the cursor is monochrome
549  if(info->hbmColor) {
550  colourBitmapSize = GetObjectA(info->hbmColor, sizeof(BITMAP), &colourBitmap);
551 
552  if(colourBitmap.bmBitsPixel != 32) {
553 
554  LOG_ERROR("Window: SetWinCursor: got some weird colour bitmapsthat have "
555  "unexpected pixel depth");
556  return;
557  }
558 
559  if(colourBitmap.bmBits != nullptr)
560  LOG_WARNING(
561  "Window: SetWinCursor: bitmap retrieve got pixel data, this will be leaked");
562 
563  monochrome = false;
564 
565  } else {
566  // And the colour cursors don't need the mask so this is only retrieved fro monochrome
567  maskBitmapSize = GetObjectA(info->hbmMask, sizeof(BITMAP), &maskBitmap);
568 
569  if(maskBitmap.bmBitsPixel != 1) {
570 
571  LOG_ERROR("Window: SetWinCursor: got some weird monochrome bitmapsthat have "
572  "unexpected pixel depth");
573  return;
574  }
575 
576  if(maskBitmap.bmBits != nullptr)
577  LOG_WARNING(
578  "Window: SetWinCursor: bitmap retrieve got pixel data, this will be leaked");
579 
580  monochrome = true;
581  }
582 
583  if((monochrome && maskBitmapSize == 0) || (!monochrome && colourBitmapSize == 0)) {
584 
585  LOG_ERROR("Window: SetWinCursor: failed to get bitmap objects");
586  return;
587  }
588 
589  const auto width = monochrome ? maskBitmap.bmWidth : colourBitmap.bmWidth;
590  const auto height = monochrome ? maskBitmap.bmHeight / 2 : colourBitmap.bmHeight;
591 
592  std::unique_ptr<SDL_Surface, void (*)(SDL_Surface*)> image(
593  SDL_CreateRGBSurfaceWithFormat(0, width, height, 32,
594  // There's some weird stuff going on with this format. This should not be changed
595  // This format is accurate for monochrome but colour cursors have the elements in a
596  // different order, for some reason
597  SDL_PIXELFORMAT_RGBA32),
598  SDL_FreeSurface);
599 
600  if(!image) {
601 
602  LOG_ERROR("Window: SetWinCursor: failed to create SDL surface");
603  return;
604  }
605 
606  // Retrieve pixel data //
607  std::unique_ptr<uint8_t[]> maskPixels;
608  std::unique_ptr<uint8_t[]> colourPixels;
609 
610  HDC hdcScreen = GetDC(nullptr);
611 
612  // BITMAPFILEHEADER bmfHeader;
613  BITMAPINFOHEADER bi;
614 
615  bi.biSize = sizeof(BITMAPINFOHEADER);
616  bi.biPlanes = 1;
617  bi.biCompression = BI_RGB; // Apparently this means also uncompressed
618  bi.biXPelsPerMeter = 0;
619  bi.biYPelsPerMeter = 0;
620  bi.biClrUsed = 0;
621  bi.biClrImportant = 0;
622 
623  bool fail = false;
624 
625  if(monochrome) {
626  // For some reason the colour cursors don't need this so save time retrieving
627 
628  bi.biWidth = maskBitmap.bmWidth;
629  bi.biHeight = -maskBitmap.bmHeight;
630  bi.biBitCount = 1;
631  const auto maskByteCount = (maskBitmap.bmWidth / 8) * maskBitmap.bmHeight;
632  bi.biSizeImage = maskByteCount;
633 
634  maskPixels = std::unique_ptr<uint8_t[]>(new uint8_t[maskByteCount]);
635 
636  int readBytes = GetDIBits(hdcScreen, info->hbmMask, 0, maskBitmap.bmHeight,
637  maskPixels.get(), (BITMAPINFO*)&bi, DIB_RGB_COLORS) *
638  maskBitmap.bmWidthBytes;
639  if(readBytes != maskByteCount) {
640  LOG_ERROR("Window: SetWinCursor: pixel read count is different (read) " +
641  std::to_string(readBytes) + " != " + std::to_string(maskByteCount));
642  fail = true;
643  }
644  } else {
645 
646  bi.biWidth = colourBitmap.bmWidth;
647  bi.biHeight = -colourBitmap.bmHeight;
648  bi.biBitCount = 32;
649  const auto colourByteCount = (colourBitmap.bmWidth * 4) * colourBitmap.bmHeight;
650  bi.biSizeImage = colourByteCount;
651 
652  colourPixels = std::unique_ptr<uint8_t[]>(new uint8_t[colourByteCount]);
653 
654  int readBytes = GetDIBits(hdcScreen, info->hbmColor, 0, colourBitmap.bmHeight,
655  colourPixels.get(), (BITMAPINFO*)&bi, DIB_RGB_COLORS) *
656  colourBitmap.bmWidthBytes;
657  if(readBytes != colourByteCount) {
658  LOG_ERROR("Window: SetWinCursor: pixel read count is different (read) " +
659  std::to_string(readBytes) + " != " + std::to_string(colourByteCount));
660  fail = true;
661  }
662  }
663 
664  ReleaseDC(nullptr, hdcScreen);
665 
666  if(fail) {
667  LOG_ERROR("Window: SetWinCursor: failed to read bitmap pixels");
668  return;
669  }
670 
671  uint8_t* pixels = static_cast<uint8_t*>(image->pixels);
672  uint8_t* directMask = maskPixels.get();
673  uint8_t* directColour = colourPixels.get();
674 
675  // This if is outside the loop to not constantly hit it
676  if(monochrome) {
677  for(size_t y = 0; y < height; ++y) {
678  for(size_t x = 0; x < width; ++x) {
679 
680  const auto pixelStart = y * width * 4 + (x * 4);
681 
682  const auto maskBit = (maskBitmap.bmWidthBytes * 8 * y) + x;
683  const auto maskIndex = maskBit / 8;
684  const auto indexInsideByte = maskBit % 8;
685  const auto insideBitAnd = (0x80 >> indexInsideByte);
686 
687  const uint8_t maskByte = directMask[maskIndex];
688  const bool visible = (maskByte & insideBitAnd) > 0;
689  const auto mask = visible ? 255 : 0;
690 
691  const auto colourIndex = maskIndex + (maskBitmap.bmWidthBytes * height);
692  const auto sourceByte = directMask[colourIndex];
693  const bool sourcePixel = (sourceByte & insideBitAnd) > 0;
694  const auto pixel = sourcePixel ? 255 : 0;
695 
696  // Red
697  pixels[pixelStart + 0] = 0;
698  // Green
699  pixels[pixelStart + 1] = 0;
700  // Blue
701  pixels[pixelStart + 2] = 0;
702  // Alpha
703  pixels[pixelStart + 3] = pixel & mask;
704  }
705  }
706  } else {
707  for(size_t y = 0; y < height; ++y) {
708  for(size_t x = 0; x < width; ++x) {
709 
710  const auto pixelStart = y * width * 4 + (x * 4);
711 
712  const auto sourcePixelStart = y * colourBitmap.bmWidthBytes + (x * 4);
713 
714  // Weird pixel order. This doesn't match the format for some reason
715  // So we just copy the data from the HBITMAP and hope it is right
716  // Also apparently applying the mask breaks everything
717  // Red
718  pixels[pixelStart + 0] = directColour[sourcePixelStart + 0] /*& mask*/;
719  // Green
720  pixels[pixelStart + 1] = directColour[sourcePixelStart + 1] /*& mask*/;
721  // Blue
722  pixels[pixelStart + 2] = directColour[sourcePixelStart + 2] /*& mask*/;
723  // Alpha
724  pixels[pixelStart + 3] = directColour[sourcePixelStart + 3] /*& mask*/;
725  }
726  }
727  }
728 
729  std::unique_ptr<SDL_Cursor, void (*)(SDL_Cursor*)> newCursor(
730  SDL_CreateColorCursor(image.get(), info->xHotspot, info->yHotspot), SDL_FreeCursor);
731 
732  if(!newCursor) {
733  LOG_ERROR("Window: SetWinCursor: failed to create sdl cursor");
734  return;
735  }
736 
737  SDL_SetCursor(newCursor.get());
738 
739  // Overwrite the old ones releasing the resources after we are using the new cursor
740  CurrentCursor = std::move(newCursor);
741  CurrentCursorImage = std::move(image);
742 }
743 #endif //_WIN32
744 // ------------------------------------ //
746 {
747  if(MouseCaptureState == state)
748  return true;
749 
750  MouseCaptureState = state;
751 
752  // handle changing state //
753  if(!MouseCaptureState) {
754 
755  // set mouse visible and disable capturing //
756  SDL_SetRelativeMouseMode(SDL_FALSE);
757  // reset pointer to indicate that this object no longer captures mouse to this
758  // window
759  // //
760  InputCapturer = nullptr;
761 
762  } else {
763 
764  if(InputCapturer != this && InputCapturer != nullptr) {
765  // another window has input //
766  MouseCaptureState = false;
767  return false;
768  }
769 
770  SDL_SetRelativeMouseMode(SDL_TRUE);
771 
772  // hide mouse and tell window to capture //
773  // DisplayWindow->SetMouseToCenter();
774 
775  // set static ptr to this //
776  InputCapturer = this;
777  }
778  return true;
779 }
780 
781 
783 {
784  ApplicationWantCursorState = toset;
785 
786  if(!ApplicationWantCursorState || ForceMouseVisible) {
787  // show cursor //
788  if(!CursorState) {
789  CursorState = true;
790  Logger::Get()->Info("Showing cursor");
791 
792  // Don't do anything if window is not valid anymore //
793  if(!SDLWindow)
794  return;
795 
796  SDL_ShowCursor(SDL_ENABLE);
797  }
798  } else {
799  // hide cursor //
800  if(CursorState) {
801 
802  CursorState = false;
803  Logger::Get()->Info("Hiding cursor");
804 
805  // Don't do anything if window is not valid anymore //
806  if(!SDLWindow)
807  return;
808 
809  SDL_ShowCursor(SDL_DISABLE);
810  }
811  }
812 }
813 
815 {
816  int32_t width, height;
817  GetSize(width, height);
818  SDL_WarpMouseInWindow(SDLWindow, width / 2, height / 2);
819 }
820 
821 DLLEXPORT void Window::GetRelativeMouse(int& x, int& y) const
822 {
823  if(!SDLWindow)
824  return;
825 
826  int globalX, globalY;
827  SDL_GetGlobalMouseState(&globalX, &globalY);
828 
829  int windowX, windowY;
830  SDL_GetWindowPosition(SDLWindow, &windowX, &windowY);
831 
832 
833  globalX -= windowX;
834  globalY -= windowY;
835 
836  int32_t width, height;
837  GetSize(width, height);
838 
839  x = std::clamp(globalX, 0, width);
840  y = std::clamp(globalY, 0, height);
841 }
842 
844 {
845  if(!SDLWindow)
846  return;
847 
848  int globalX, globalY;
849  SDL_GetGlobalMouseState(&globalX, &globalY);
850 
851  int windowX, windowY;
852  SDL_GetWindowPosition(SDLWindow, &windowX, &windowY);
853 
854  x = globalX - windowX;
855  y = globalY - windowY;
856 }
857 
858 
859 DLLEXPORT void Window::GetNormalizedRelativeMouse(float& x, float& y) const
860 {
861  int xInt, yInt;
862  GetRelativeMouse(xInt, yInt);
863 
864  int32_t width, height;
865  GetSize(width, height);
866 
867  if(width == 0 || height == 0) {
868  x = 0.5f;
869  y = 0.5f;
870  return;
871  }
872 
873  x = static_cast<float>(xInt) / width;
874  y = static_cast<float>(yInt) / height;
875 }
876 
878 {
879  int X, Y;
881 
882  int32_t width, height;
883  GetSize(width, height);
884 
885  // check the coordinates //
886 
887  if(X < 0 || Y < 0 || X > width || Y > height) {
888  return true;
889  }
890 
891  return false;
892 }
893 // ------------------------------------ //
894 DLLEXPORT void Window::GetSize(int32_t& width, int32_t& height) const
895 {
896  SDL_GetWindowSize(SDLWindow, &width, &height);
897 }
898 
899 DLLEXPORT void Window::GetPosition(int32_t& x, int32_t& y) const
900 {
901  SDL_GetWindowPosition(SDLWindow, &x, &y);
902 }
903 // ------------------------------------ //
905 {
906  if(!SDLWindow)
907  return -1;
908 
909  return SDL_GetWindowID(SDLWindow);
910 }
911 
912 #ifdef _WIN32
914 #else
915 DLLEXPORT unsigned long Window::GetNativeHandle() const
916 #endif //_WIN32
917 {
918  SDL_SysWMinfo wmInfo;
919  SDL_VERSION(&wmInfo.version);
920  if(!SDL_GetWindowWMInfo(SDLWindow, &wmInfo)) {
921 
922  LOG_FATAL("Window: GetNativeHandle: failed to retrieve wm info");
923 #ifdef _WIN32
924  return 0;
925 #else
926  return -1;
927 #endif //_WIN32
928  }
929 #if defined(_WIN32)
930  return wmInfo.info.win.window;
931 #else
932 #if defined(__linux)
933  return wmInfo.info.x11.window;
934 #else
935 #error Fix this for mac
936 #endif
937 #endif
938 }
939 
940 #if defined(__linux)
941 DLLEXPORT unsigned long Window::GetWindowXDisplay() const
942 {
943  SDL_SysWMinfo wmInfo;
944  SDL_VERSION(&wmInfo.version);
945  if(!SDL_GetWindowWMInfo(SDLWindow, &wmInfo)) {
946 
947  LOG_FATAL("Window: GetWindowXDisplay: failed to retrieve wm info");
948  return -1;
949  }
950 
951  return (unsigned long)wmInfo.info.x11.display;
952 }
953 #endif
954 // ------------------------------------ //
955 DLLEXPORT void Window::SaveScreenShot(const std::string& filename)
956 {
957  // uses render target's capability to save it's contents //
958  DEBUG_BREAK;
959  // GetOgreWindow()->writeContentsToTimestampedFile(filename, "_window1.png");
960 }
961 
963 {
964  DEBUG_BREAK;
965  return false;
966  // return OWindow->isVSyncEnabled();
967 }
968 
969 DLLEXPORT void Window::SetCustomInputController(std::shared_ptr<InputController> controller)
970 {
971  TertiaryReceiver = controller;
972 }
973 // ------------------------------------ //
975 {
976  // Don't pass to GUI if mouse capture is enabled
977  if(MouseCaptured)
978  return nullptr;
979 
980  auto layer = WindowsGui->GetTargetLayerForInput(type, mousex, mousey);
981  return layer;
982 }
983 
985 {
986  // Quit if window closed //
987  if(!SDLWindow) {
988 
989  LOG_WARNING("Window: GatherInput: window is closed");
990  return;
991  }
992 
993  InputGatherStarted = true;
994 
996 
997  SpecialKeyModifiers = 0;
998  CEFSpecialKeyModifiers = EVENTFLAG_NONE;
999 
1000  const auto mods = SDL_GetModState();
1001 
1002  if(mods & KMOD_CTRL) {
1003  SpecialKeyModifiers |= KEYSPECIAL_CTRL;
1004  CEFSpecialKeyModifiers |= EVENTFLAG_CONTROL_DOWN;
1005  }
1006  if(mods & KMOD_ALT) {
1007  SpecialKeyModifiers |= KEYSPECIAL_ALT;
1008  CEFSpecialKeyModifiers |= EVENTFLAG_ALT_DOWN;
1009  }
1010  if(mods & KMOD_SHIFT) {
1011  SpecialKeyModifiers |= KEYSPECIAL_SHIFT;
1012  CEFSpecialKeyModifiers |= EVENTFLAG_SHIFT_DOWN;
1013  }
1014  if(mods & KMOD_CAPS) {
1015  SpecialKeyModifiers |= KEYSPECIAL_CAPS;
1016  CEFSpecialKeyModifiers |= EVENTFLAG_CAPS_LOCK_ON;
1017  }
1018  if(mods & KMOD_GUI) {
1019  SpecialKeyModifiers |= KEYSPECIAL_SUPER;
1020  }
1021 
1022  // TODO: EVENTFLAG_NUM_LOCK_ON;
1023 
1024  // TODO: fix mouse capture
1025  // // Handle mouse capture
1026  // if(MouseCaptured && Focused) {
1027 
1028  // // get mouse relative to window center //
1029  // int xmoved = 0, ymoved = 0;
1030 
1031  // SDL_GetRelativeMouseState(&xmoved, &ymoved);
1032 
1033  // // Pass input //
1034  // GetInputController()->SendMouseMovement(xmoved, ymoved);
1035  // }
1036 }
1037 
1039 {
1040  // Initial mouse position
1041  if(!InitialMousePositionSent) {
1043 
1044  InitialMousePositionSent = true;
1045 
1046  // Build a mouse move from the current position and send it
1047  int x;
1048  int y;
1049  GetRelativeMouse(x, y);
1050 
1051  SDL_Event event;
1052 
1053  event.motion.x = x;
1054  event.motion.y = y;
1055  InjectMouseMove(event);
1056  }
1057  }
1058 
1059  InputGatherStarted = false;
1060 }
1061 
1063 {
1064  const bool outsideArea = IsMouseOutsideWindowClientArea();
1065 
1066  // force cursor visible check (if outside client area or mouse is unfocused on the
1067  // window)
1068  if(outsideArea || !Focused) {
1069 
1070  ForceMouseVisible = true;
1071 
1072  } else {
1073 
1074  ForceMouseVisible = false;
1075  }
1076 
1077  // update cursor state //
1078  SetHideCursor(ApplicationWantCursorState);
1079 }
1080 // ------------------ Input listener functions ------------------ //
1082  const SDL_Event& sdlevent, bool down, bool textinput, int mousex, int mousey)
1083 {
1084  // Find active gui view that wants the event
1085  auto* receiver = GetGUIEventReceiver(GUI::INPUT_EVENT_TYPE::Keypress, mousex, mousey);
1086 
1087  // Don't pass to GUI
1088  if(!receiver)
1089  return false;
1090 
1091  return receiver->OnReceiveKeyInput(sdlevent, down, textinput, mousex, mousey,
1092  SpecialKeyModifiers, CEFSpecialKeyModifiers);
1093 }
1094 
1095 DLLEXPORT void Window::InjectMouseMove(const SDL_Event& event)
1096 {
1097  if(!InputGatherStarted)
1099 
1100  // Only pass this data if we aren't going to pass our own captured mouse //
1101  if(!MouseCaptured) {
1102 
1103  auto* receiver =
1104  GetGUIEventReceiver(GUI::INPUT_EVENT_TYPE::Other, event.motion.x, event.motion.y);
1105 
1106  if(receiver) {
1107  // TODO: IsMouseOutsideWindowClientArea needs to be
1108  // updated (for CEF GuiViews) once we support multiple
1109  // browsers in a single window
1110  receiver->OnMouseMove(event, IsMouseOutsideWindowClientArea());
1111  }
1112  }
1113 
1115 }
1116 
1117 DLLEXPORT void Window::InjectMouseWheel(const SDL_Event& event)
1118 {
1119  if(!InputGatherStarted)
1121 
1122  if(!MouseCaptured) {
1123 
1124  int mouseX;
1125  int mouseY;
1126  GetRelativeMouse(mouseX, mouseY);
1127  // TODO: allow configuring if mouse wheel is considered a key
1128  auto* receiver = GetGUIEventReceiver(GUI::INPUT_EVENT_TYPE::Scroll, mouseX, mouseY);
1129 
1130  if(receiver) {
1131 
1132  receiver->OnMouseWheel(event);
1133 
1134  } else {
1135 
1136  int x = event.wheel.x;
1137  int y = event.wheel.y;
1138 
1139  if(SDL_MOUSEWHEEL_FLIPPED == event.wheel.direction) {
1140  y *= -1;
1141  } else {
1142  x *= -1;
1143  }
1144 
1145  GetInputController()->OnScroll(x, y, SpecialKeyModifiers);
1146  }
1147  }
1148 }
1149 
1150 DLLEXPORT void Window::InjectMouseButtonDown(const SDL_Event& event)
1151 {
1152  if(!InputGatherStarted)
1154 
1155  if(!MouseCaptured) {
1156 
1157  auto* receiver =
1158  GetGUIEventReceiver(GUI::INPUT_EVENT_TYPE::Other, event.button.x, event.button.y);
1159 
1160  if(receiver) {
1161 
1162  receiver->OnMouseButton(event, true);
1163  }
1164  }
1165 }
1166 
1167 DLLEXPORT void Window::InjectMouseButtonUp(const SDL_Event& event)
1168 {
1169  if(!InputGatherStarted)
1171 
1172  if(!MouseCaptured) {
1173 
1174  auto* receiver =
1175  GetGUIEventReceiver(GUI::INPUT_EVENT_TYPE::Other, event.button.x, event.button.y);
1176 
1177  if(receiver) {
1178 
1179  receiver->OnMouseButton(event, false);
1180  }
1181  }
1182 }
1183 
1184 DLLEXPORT void Window::InjectCodePoint(const SDL_Event& event)
1185 {
1186  if(!InputGatherStarted)
1188 
1189  // Try to pass to CEF //
1190  int mouseX;
1191  int mouseY;
1192  GetRelativeMouse(mouseX, mouseY);
1193 
1194  if(!DoGUILayerInputPass(event, true, true, mouseX, mouseY)) {
1195 
1196  // GUI didn't want it
1197  }
1198 }
1199 
1200 DLLEXPORT void Window::InjectKeyDown(const SDL_Event& event)
1201 {
1202  if(!InputGatherStarted)
1204 
1205  bool SentToController = false;
1206 
1207  // Try to pass to CEF //
1208  int mouseX;
1209  int mouseY;
1210  GetRelativeMouse(mouseX, mouseY);
1211 
1212  const auto handled = DoGUILayerInputPass(event, true, false, mouseX, mouseY);
1213 
1214  if(!handled) {
1215 
1216  // CEF didn't want it
1217  // Then try disabling collections //
1218  // LOG_WRITE("TODO: check is a text box active");
1219  // if(!OwningWindow->GetGui()->ProcessKeyDown(sdlkey, SpecialKeyModifiers)) {
1220 
1221  // Finally send to a controller //
1222  SentToController = true;
1223  GetInputController()->OnInputGet(event.key.keysym.sym, SpecialKeyModifiers, true);
1224  // }
1225  }
1226 
1227  if(!SentToController) {
1228  GetInputController()->OnBlockedInput(event.key.keysym.sym, SpecialKeyModifiers, true);
1229  }
1230 }
1231 
1232 DLLEXPORT void Window::InjectKeyUp(const SDL_Event& event)
1233 {
1234  if(!InputGatherStarted)
1236 
1237  // Send to CEF if GUI is active //
1238  int mouseX;
1239  int mouseY;
1240  GetRelativeMouse(mouseX, mouseY);
1241  DoGUILayerInputPass(event, false, false, mouseX, mouseY);
1242 
1243  // This should always be passed here //
1244  GetInputController()->OnInputGet(event.key.keysym.sym, SpecialKeyModifiers, false);
1245 }
1246 
1247 } // namespace Leviathan
DLLEXPORT unsigned long GetNativeHandle() const
Definition: Window.cpp:915
std::chrono::duration< int64_t, std::milli > MillisecondDuration
Definition: TimeIncludes.h:13
DLLEXPORT void Invoke(const std::function< void()> &function)
Runs function on the main thread before the next tick.
Definition: Engine.cpp:1164
DLLEXPORT void GetSize(int32_t &width, int32_t &height) const
Definition: Window.cpp:894
#define LOG_INFO(x)
Definition: Define.h:88
bs::SPtr< bs::RenderWindow > RegisterCreatedWindow(Window &window)
Called when Window objects are created to register them with bsf and with the case of the first windo...
Definition: Graphics.cpp:394
DLLEXPORT void Info(const std::string &data) override
Definition: Logger.cpp:164
#define LOG_ERROR(x)
Definition: Define.h:90
DLLEXPORT void OnFocusChange(bool focused)
Definition: Window.cpp:326
DLLEXPORT void InjectMouseButtonUp(const SDL_Event &event)
Definition: Window.cpp:1167
#define LOG_FATAL(x)
Definition: Define.h:92
DLLEXPORT void QueueTask(std::shared_ptr< QueuedTask > task)
Adds a task to the queue.
DLLEXPORT void OnResize(int width, int height)
Definition: Window.cpp:302
DLLEXPORT void GetPosition(int32_t &x, int32_t &y) const
Definition: Window.cpp:899
Manages registering multiple InputReceiver objects to a window to easily control where input is sent...
DLLEXPORT void InjectKeyDown(const SDL_Event &event)
Definition: Window.cpp:1200
DLLEXPORT void SetHideCursor(bool toset)
Definition: Window.cpp:782
DLLEXPORT void _StartGatherInput()
Detects state of modifier keys. Called whenever input is injected and is stored until InputEnd is cal...
Definition: Window.cpp:984
static DLLEXPORT ThreadingManager * Get()
DLLEXPORT Window(Graphics *windowcreater, AppDef *windowproperties)
Definition: Window.cpp:53
DLLEXPORT bool Render(int mspassed, int tick, int timeintick)
This function uses the LinkObjects function objects.
Definition: Window.cpp:290
DLLEXPORT WindowDataDetails & GetWindowDetails()
Definition: AppDefine.h:93
Base class for all exceptions thrown by Leviathan.
Definition: Exceptions.h:10
#define LOG_WARNING(x)
Definition: Define.h:89
DLLEXPORT void InjectMouseMove(const SDL_Event &event)
Definition: Window.cpp:1095
DLLEXPORT void Tick(int mspassed)
Called by Engine.
Definition: Window.cpp:284
bool DoGUILayerInputPass(const SDL_Event &sdlevent, bool down, bool textinput, int mousex, int mousey)
Definition: Window.cpp:1081
unsigned char uint8_t
Definition: core.h:38
GUI::Layer * GetGUIEventReceiver(GUI::INPUT_EVENT_TYPE type, int mousex, int mousey)
Retrieves the active gui object that is going to receive an event.
Definition: Window.cpp:974
Base class for WidgetLayer and View (browser containers / CEF) to add to a GuiManager.
Definition: GuiLayer.h:18
#define LOG_WRITE(x)
Definition: Define.h:91
DLLEXPORT void InputEnd()
Translates a client space coordinate to screen coordinate.
Definition: Window.cpp:1038
virtual DLLEXPORT void OnBlockedInput(int32_t key, int specialmodifiers, bool down)
This is called when input is not received.
DLLEXPORT void InjectKeyUp(const SDL_Event &event)
Definition: Window.cpp:1232
#define LEVIATHAN_ASSERT(x, msg)
Definition: Define.h:98
static std::string ToString(const T &val)
Definition: Convert.h:72
DLLEXPORT uint32_t GetSDLID() const
Definition: Window.cpp:904
void _CheckMouseVisibilityStates()
Definition: Window.cpp:1062
virtual DLLEXPORT void StartInputGather()
DLLEXPORT void LinkObjects(std::shared_ptr< GameWorld > world)
This function also updates the camera aspect ratio.
Definition: Window.cpp:256
DLLEXPORT void GetUnclampedRelativeMouse(int &x, int &y) const
Definition: Window.cpp:843
DLLEXPORT void SaveScreenShot(const std::string &filename)
Definition: Window.cpp:955
DLLEXPORT void GetRelativeMouse(int &x, int &y) const
Definition: Window.cpp:821
static DLLEXPORT Logger * Get()
Definition: Logger.cpp:106
DLLEXPORT int GetWindowNumber() const
Definition: Window.h:173
DLLEXPORT void UnlinkAll()
Definition: Window.cpp:279
DLLEXPORT void InjectMouseButtonDown(const SDL_Event &event)
Definition: Window.cpp:1150
virtual DLLEXPORT void OnInputGet(int32_t key, int specialmodifiers, bool down)
Called when the GUI didn&#39;t eat the whole keypress (it only ate half of the A-press) ...
DLLEXPORT ~Window()
Definition: Window.cpp:207
DLLEXPORT bool IsMouseOutsideWindowClientArea() const
Definition: Window.cpp:877
DLLEXPORT bool SetMouseCapture(bool state)
Definition: Window.cpp:745
DLLEXPORT void SetCustomInputController(std::shared_ptr< InputController > controller)
Overwrites the default InputController with a custom one.
Definition: Window.cpp:969
#define DLLEXPORT
Definition: Include.h:84
DLLEXPORT InputController * GetInputController()
Definition: Window.h:160
virtual DLLEXPORT void OnScroll(int x, int y, int modifiers)
DLLEXPORT void InjectCodePoint(const SDL_Event &event)
Injects text.
Definition: Window.cpp:1184
static DLLEXPORT Engine * Get()
Definition: Engine.cpp:85
DLLEXPORT void GetNormalizedRelativeMouse(float &x, float &y) const
Definition: Window.cpp:859
The access mask controls which registered functions and classes a script sees.
Definition: GameModule.h:12
unsigned int uint32_t
Definition: core.h:40
DLLEXPORT void SetMouseToCenter()
Definition: Window.cpp:814
DLLEXPORT void AssertIfNotMainThread() const
Asserts if not called on the main thread.
Definition: Engine.h:105
DLLEXPORT bool GetVsync() const
Definition: Window.cpp:962
DLLEXPORT void InjectMouseWheel(const SDL_Event &event)
Definition: Window.cpp:1117
bs::HSceneObject WindowSceneObject
Definition: Window.cpp:42
DLLEXPORT void BringToFront()
Tries to bring this window to front.
Definition: Window.cpp:346