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