Mouse.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************/
  5. #define SELECT_DIST_2 Sqr(0.013f)
  6. #define SPEED 0.005f
  7. #define BUF_BUTTONS 256
  8. #if WINDOWS_OLD
  9. enum COOP_MODE
  10. {
  11. BACKGROUND,
  12. FOREGROUND,
  13. MOUSE_MODE=BACKGROUND, // use background mode so we can get correct information about relative movement
  14. };
  15. #define MS_RAW_INPUT 1
  16. #elif WINDOWS_NEW
  17. using namespace Windows::System;
  18. using namespace Windows::UI::Core;
  19. #endif
  20. /******************************************************************************
  21. #if WINDOWS_OLD
  22. #define MOUSEEVENTF_MASK 0xFFFFFF00
  23. #define MOUSEEVENTF_FROMTOUCH 0xFF515700
  24. #define SET_HOOK 1
  25. static HHOOK MsHook;
  26. static LRESULT CALLBACK MsLLProc(int nCode, WPARAM wParam, LPARAM lParam)
  27. {
  28. if(nCode>=0)
  29. {
  30. //MSLLHOOKSTRUCT &ms=*(MSLLHOOKSTRUCT*)lParam;
  31. MOUSEHOOKSTRUCT &ms=*(MOUSEHOOKSTRUCT*)lParam;
  32. if((ms.dwExtraInfo&MOUSEEVENTF_MASK)==MOUSEEVENTF_FROMTOUCH)
  33. return 1;
  34. }
  35. return CallNextHookEx(MsHook, nCode, wParam, lParam);
  36. }
  37. static void SetHook() {if(!MsHook)MsHook=SetWindowsHookEx(WH_MOUSE_LL, MsLLProc, App._hinstance, 0);}
  38. //static void SetHook() {if(!MsHook)MsHook=SetWindowsHookEx(WH_MOUSE, MsLLProc, App._hinstance, GetCurrentThreadId());}
  39. static void UnHook() {if( MsHook){UnhookWindowsHookEx(MsHook); MsHook=null;}}
  40. #endif
  41. /******************************************************************************/
  42. struct MouseCursor
  43. {
  44. void del (); // delete manually
  45. Bool create(C Image &image, C VecI2 &hot_spot=VecI2(0, 0)); // create from image, false on fail
  46. ~MouseCursor() {del();}
  47. #if LINUX
  48. MouseCursor() { _cursor =NULL;}
  49. Bool is()C {return _cursor!=NULL;}
  50. #else
  51. MouseCursor() { _cursor =null;}
  52. Bool is()C {return _cursor!=null;}
  53. #endif
  54. #if EE_PRIVATE
  55. #if WINDOWS_OLD
  56. HCURSOR _cursor;
  57. #elif WINDOWS_NEW
  58. CoreCursor ^_cursor;
  59. #elif MAC
  60. NSCursor *_cursor;
  61. #elif LINUX
  62. XCursor _cursor;
  63. #else
  64. Ptr _cursor;
  65. #endif
  66. #else
  67. private:
  68. Ptr _cursor;
  69. #endif
  70. Image _image;
  71. NO_COPY_CONSTRUCTOR(MouseCursor);
  72. };
  73. /******************************************************************************/
  74. void MouseCursor::del()
  75. {
  76. #if WINDOWS_OLD
  77. if(_cursor){DestroyIcon(_cursor); _cursor=null;}
  78. #elif WINDOWS_NEW
  79. _cursor=null;
  80. #elif MAC
  81. [_cursor release]; _cursor=null;
  82. #elif LINUX
  83. if(_cursor){if(XDisplay)XFreeCursor(XDisplay, _cursor); _cursor=NULL;}
  84. #endif
  85. _image.del();
  86. }
  87. Bool MouseCursor::create(C Image &image, C VecI2 &hot_spot)
  88. {
  89. del();
  90. #if WINDOWS_OLD
  91. _cursor=CreateIcon(image, &hot_spot);
  92. #elif WINDOWS_NEW
  93. // TODO: WINDOWS_NEW currently there's no way to dynamically create a 'CoreCursor'
  94. #elif MAC // Mac must keep the cursor image data, it is stored in 'T._image', if released then cursor image data gets corrupted, that's why it must be kept in memory (yes that was tested)
  95. image.copy(_image, -1, -1, 1, IMAGE_R8G8B8A8, IMAGE_SOFT, 1);
  96. REPD(y, _image.h())
  97. REPD(x, _image.w())
  98. {
  99. // premultiply alpha (required by Mac OS)
  100. Color c=_image.color(x, y);
  101. c.r=c.r*c.a/255;
  102. c.g=c.g*c.a/255;
  103. c.b=c.b*c.a/255;
  104. _image.color(x, y, c);
  105. }
  106. unsigned char *image_data=_image.data();
  107. if(NSBitmapImageRep *bitmap=[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&image_data
  108. pixelsWide:_image.w()
  109. pixelsHigh:_image.h()
  110. bitsPerSample:8
  111. samplesPerPixel:4
  112. hasAlpha:YES
  113. isPlanar:NO
  114. colorSpaceName:NSCalibratedRGBColorSpace
  115. bytesPerRow:_image.pitch()
  116. bitsPerPixel:32])
  117. {
  118. if(NSImage *ns_image=[[NSImage alloc] initWithSize:NSMakeSize(_image.w(), _image.h())])
  119. {
  120. [ns_image addRepresentation:bitmap];
  121. NSPoint point; point.x=hot_spot.x; point.y=hot_spot.y;
  122. _cursor=[[NSCursor alloc] initWithImage:ns_image hotSpot:point];
  123. [ns_image release];
  124. }
  125. [bitmap release];
  126. }
  127. #elif LINUX
  128. if(XDisplay)
  129. {
  130. Image temp; C Image *src=ℑ
  131. if(src->compressed())if(src->copyTry(temp, -1, -1, 1, IMAGE_B8G8R8A8, IMAGE_SOFT, 1))src=&temp;else src=null;
  132. if(src && src->lockRead())
  133. {
  134. if(XcursorImage *image=XcursorImageCreate(src->w(), src->h()))
  135. {
  136. image->xhot =hot_spot.x;
  137. image->yhot =hot_spot.y;
  138. image->delay=0;
  139. VecB4 *bgra=(VecB4*)image->pixels;
  140. FREPD(y, src->h())
  141. FREPD(x, src->w())
  142. {
  143. Color c=src->color(x, y);
  144. bgra++->set(c.b, c.g, c.r, c.a);
  145. }
  146. _cursor=XcursorImageLoadCursor(XDisplay, image);
  147. XcursorImageDestroy(image);
  148. }
  149. src->unlock();
  150. }
  151. }
  152. #elif WEB
  153. // TODO: Web mouse cursor
  154. #endif
  155. #if LINUX
  156. return _cursor!=NULL;
  157. #else
  158. return _cursor!=null;
  159. #endif
  160. }
  161. /******************************************************************************/
  162. #if WINDOWS_OLD
  163. static const Byte Keys[]=
  164. {
  165. VK_LBUTTON ,
  166. VK_RBUTTON ,
  167. VK_MBUTTON ,
  168. VK_XBUTTON1,
  169. VK_XBUTTON2,
  170. };
  171. #elif WINDOWS_NEW
  172. static const Byte Keys[]=
  173. {
  174. (Byte)VirtualKey::LeftButton,
  175. (Byte)VirtualKey::RightButton,
  176. (Byte)VirtualKey::MiddleButton,
  177. (Byte)VirtualKey::XButton1,
  178. (Byte)VirtualKey::XButton2,
  179. };
  180. #elif MAC
  181. VecI2 MouseIgnore;
  182. static Bool MouseClipOn;
  183. static RectI MouseClipRect;
  184. #elif LINUX
  185. static MouseCursor MsCurEmpty;
  186. static XWindow Grab;
  187. #endif
  188. static Bool DelayPush;
  189. Mouse Ms;
  190. MouseCursor MsCur;
  191. /******************************************************************************/
  192. Mouse::Mouse()
  193. {
  194. REPAO(_button)=0;
  195. _selecting=_dragging=_first=_detected=_on_client=_freezed=_clip_rect_on=_clip_window=_freeze=_action=_locked=false;
  196. _visible=_want_cur_hw=true;
  197. _cur=-1;
  198. _speed=SPEED; _wheel_time=_start_time=0;
  199. _pos=_delta=_delta_clp=_delta_relative=_start_pos=_wheel=_wheel_f=0;
  200. _window_posi=_desktop_posi=_deltai=_hot_spot=_wheel_i=0;
  201. _clip_rect.zero();
  202. _did=null;
  203. _button_name[0]="Mouse1";
  204. _button_name[1]="Mouse2";
  205. _button_name[2]="Mouse3";
  206. _button_name[3]="Mouse4";
  207. _button_name[4]="Mouse5";
  208. _button_name[5]="Mouse6";
  209. _button_name[6]="Mouse7";
  210. _button_name[7]="Mouse8";
  211. }
  212. void Mouse::del()
  213. {
  214. _image=null; // clear the pointer because display and images are already deleted, and attempting to use it afterwards will result in a crash
  215. #if WINDOWS_OLD
  216. #if MS_RAW_INPUT
  217. RAWINPUTDEVICE rid[1];
  218. rid[0].usUsagePage=0x01;
  219. rid[0].usUsage =0x02; // mouse
  220. rid[0].dwFlags =RIDEV_REMOVE;
  221. rid[0].hwndTarget =App.Hwnd();
  222. RegisterRawInputDevices(rid, Elms(rid), SIZE(RAWINPUTDEVICE));
  223. #else
  224. RELEASE(_did);
  225. #endif
  226. #elif LINUX
  227. if(Grab){XDestroyWindow(XDisplay, Grab); Grab=NULL;}
  228. #endif
  229. }
  230. void Mouse::create()
  231. {
  232. if(LogInit)LogN("Mouse.create");
  233. #if WINDOWS_OLD
  234. #if MS_RAW_INPUT
  235. RAWINPUTDEVICE rid[1];
  236. rid[0].usUsagePage=0x01;
  237. rid[0].usUsage =0x02; // mouse
  238. rid[0].dwFlags =((MOUSE_MODE==BACKGROUND) ? RIDEV_INPUTSINK : 0);
  239. rid[0].hwndTarget =App.Hwnd();
  240. RegisterRawInputDevices(rid, Elms(rid), SIZE(RAWINPUTDEVICE));
  241. Memt<RAWINPUTDEVICELIST> devices;
  242. UINT num_devices=0; GetRawInputDeviceList(null, &num_devices, SIZE(RAWINPUTDEVICELIST));
  243. again:
  244. devices.setNum(num_devices);
  245. Int out=GetRawInputDeviceList(devices.data(), &num_devices, SIZE(RAWINPUTDEVICELIST));
  246. if(out<0) // error
  247. {
  248. if(Int(num_devices)>devices.elms())goto again; // need more memory
  249. devices.clear();
  250. }else
  251. {
  252. if(out<devices.elms())devices.setNum(out);
  253. FREPA(devices)
  254. {
  255. C RAWINPUTDEVICELIST &device=devices[i];
  256. if(device.dwType==RIM_TYPEMOUSE){_detected=true; break;}
  257. /*UInt size=0; if(Int(GetRawInputDeviceInfoW(device.hDevice, RIDI_DEVICENAME, null, &size))>=0)
  258. {
  259. Memt<Char> name; name.setNum(size+1); Int r=GetRawInputDeviceInfoW(device.hDevice, RIDI_DEVICENAME, name.data(), &size);
  260. if(r>=0 && size==r && r+1==name.elms())
  261. {
  262. name.last()='\0'; // in case it's needed
  263. Str n=name.data();
  264. }
  265. }*/
  266. }
  267. }
  268. #else
  269. if(InputDevices.DI) // need to use DirectInput to be able to obtain '_delta_relative'
  270. if(OK(InputDevices.DI->CreateDevice(GUID_SysMouse, &_did, null)))
  271. {
  272. if(OK(_did->SetDataFormat(&c_dfDIMouse2)))
  273. if(OK(_did->SetCooperativeLevel(App.Hwnd(), DISCL_NONEXCLUSIVE|((MOUSE_MODE==FOREGROUND) ? DISCL_FOREGROUND : DISCL_BACKGROUND))))
  274. {
  275. DIPROPDWORD dipdw;
  276. dipdw.diph.dwSize =SIZE(DIPROPDWORD );
  277. dipdw.diph.dwHeaderSize=SIZE(DIPROPHEADER);
  278. dipdw.diph.dwObj =0;
  279. dipdw.diph.dwHow =DIPH_DEVICE;
  280. dipdw.dwData =BUF_BUTTONS;
  281. _did->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);
  282. if(MOUSE_MODE==BACKGROUND)_did->Acquire(); // in background mode we always want the mouse to be acquired
  283. _detected=true;
  284. goto ok;
  285. }
  286. RELEASE(_did);
  287. }
  288. ok:;
  289. #endif
  290. #elif WINDOWS_NEW
  291. update(); // 'update' to get '_window_posi' needed below, and set initial value of '_desktop_posi'
  292. _detected=(Windows::Devices::Input::MouseCapabilities().MousePresent>0); // set '_detected' after 'update' because that may set it falsely based on setting initial '_desktop_posi'
  293. _on_client=Cuts(_window_posi, RectI(0, 0, D.resW(), D.resH())); // set initial value of '_on_client', because 'OnPointerEntered' is not called at start
  294. #elif DESKTOP // assume that desktops always have a mouse
  295. _detected=true;
  296. #endif
  297. #if LINUX
  298. // create empty cursor
  299. Image temp(1, 1, 1, IMAGE_L8A8, IMAGE_SOFT, 1); temp.color(0, 0, TRANSPARENT);
  300. MsCurEmpty.create(temp);
  301. // create window for grabbing
  302. if(XDisplay)
  303. {
  304. XSetWindowAttributes win_attr; Zero(win_attr);
  305. win_attr.event_mask =ButtonPressMask|ButtonReleaseMask|StructureNotifyMask;
  306. win_attr.override_redirect=true;
  307. if(Grab=XCreateWindow(XDisplay, DefaultRootWindow(XDisplay), -1, -1, 1, 1, 0, 0, InputOnly, CopyFromParent, CWEventMask|CWOverrideRedirect, &win_attr)) // set at -1,-1 pos 1,1 size (outside of desktop, with valid size, invalid size would fail to create window)
  308. if(XMapRaised(XDisplay, Grab)==1)
  309. {
  310. XSync(XDisplay, false);
  311. REP(1024) // attempts to check if window was mapped, otherwise grabbing will fail (last time this was tested, the loop was not needed, but since other 'XCheckTypedWindowEvent' in the engine required it, then keep this one just in case)
  312. {
  313. XEvent event; if(XCheckTypedWindowEvent(XDisplay, Grab, MapNotify, &event))break;
  314. usleep(1);
  315. }
  316. }
  317. }
  318. #endif
  319. }
  320. /******************************************************************************/
  321. CChar8* Mouse::buttonName(Int x)C
  322. {
  323. return InRange(x, _button_name) ? _button_name[x] : null;
  324. }
  325. /******************************************************************************/
  326. void Mouse::speed(Flt speed) { T._speed=speed*SPEED;}
  327. Flt Mouse::speed( )C {return T._speed /SPEED;}
  328. /******************************************************************************/
  329. void Mouse::pos(C Vec2 &pos)
  330. {
  331. T._pos=pos;
  332. VecI2 posi=D.screenToWindowPixelI(pos);
  333. #if WINDOWS_OLD
  334. POINT point={posi.x, posi.y};
  335. ClientToScreen(App.Hwnd(), &point);
  336. SetCursorPos(point.x, point.y);
  337. #elif WINDOWS_NEW
  338. if(App.hwnd())
  339. {
  340. Windows::Foundation::Rect bounds=App.Hwnd()->Bounds;
  341. App.Hwnd()->PointerPosition=Windows::Foundation::Point(bounds.X+PixelsToDips(posi.x), bounds.Y+PixelsToDips(posi.y));
  342. }
  343. #elif MAC
  344. RectI client=WindowRect(true);
  345. CGPoint point; point.x=posi.x+client.min.x; point.y=posi.y+client.min.y;
  346. CGWarpMouseCursorPosition(point);
  347. #elif LINUX
  348. if(XDisplay)XWarpPointer(XDisplay, NULL, App.Hwnd(), 0, 0, 0, 0, posi.x, posi.y);
  349. #endif
  350. }
  351. /******************************************************************************/
  352. static void Clip(RectI *rect) // 'rect' is in window client space, full rect is (0, 0), (D.resW, D.resH)
  353. {
  354. // !! we can't disable clipping (set rect to null) when rect covers entire screen and in full screen, because computer can have multiple monitors connected, and we may want to prevent going to other monitors !!
  355. #if WINDOWS_OLD
  356. if(!rect)ClipCursor(null);else
  357. {
  358. RECT r;
  359. r.left=rect->min.x; r.right =rect->max.x;
  360. r.top =rect->min.y; r.bottom=rect->max.y;
  361. POINT p={0, 0}; ClientToScreen(App.Hwnd(), &p);
  362. r.left+=p.x; r.right +=p.x;
  363. r.top +=p.y; r.bottom+=p.y;
  364. ClipCursor(&r);
  365. }
  366. #elif WINDOWS_NEW
  367. /*if(App.hwnd()) // this only prevents any action taken on the title bar, like move window, minimize/maximize/close, but the cursor can still move outside the window and activate other apps on click
  368. {
  369. if(rect)App.Hwnd()-> SetPointerCapture();
  370. else App.Hwnd()->ReleasePointerCapture();
  371. }*/
  372. if(rect && !Cuts(Ms._window_posi, *rect) && App.Hwnd())
  373. {
  374. VecI2 posi=Ms._window_posi; Ms._window_posi&=*rect; posi-=Ms._window_posi;
  375. Ms._desktop_posi-=posi;
  376. Ms._deltai +=posi;
  377. Windows::Foundation::Rect bounds=App.Hwnd()->Bounds;
  378. App.Hwnd()->PointerPosition=Windows::Foundation::Point(bounds.X+PixelsToDips(Ms._window_posi.x), bounds.Y+PixelsToDips(Ms._window_posi.y));
  379. }
  380. #elif MAC
  381. if(rect)
  382. {
  383. MouseClipRect=*rect;
  384. if(MouseClipRect.max.x>MouseClipRect.min.x)MouseClipRect.max.x--; // decrease width by 1 pixel
  385. if(MouseClipRect.max.y>MouseClipRect.min.y)MouseClipRect.max.y--; // decrease height by 1 pixel
  386. }
  387. MouseClipOn=(rect!=null);
  388. CGAssociateMouseAndMouseCursorPosition(!MouseClipOn); // freeze mouse cursor when needed
  389. // !! warning: clipping using CGAssociateMouseAndMouseCursorPosition + CGWarpMouseCursorPosition will introduce delay in mouse movement, because 'CGAssociateMouseAndMouseCursorPosition' always freezes mouse, and 'CGWarpMouseCursorPosition' moves it manually based on detected input, there used to be "CGSetLocalEventsSuppressionInterval(0)" removing this delay, but it's now deprecated and its replacement doesn't affect clipping anymore
  390. #elif LINUX
  391. if(XDisplay)
  392. {
  393. if(!rect)XUngrabPointer(XDisplay, CurrentTime);else
  394. {
  395. Bool custom=(*rect!=RectI(0, 0, D.resW(), D.resH()));
  396. if( custom)
  397. {
  398. VecI2 pos=0; XWindow child=NULL; XTranslateCoordinates(XDisplay, App.Hwnd(), DefaultRootWindow(XDisplay), 0, 0, &pos.x, &pos.y, &child); // convert to desktop space
  399. XMoveResizeWindow(XDisplay, Grab, rect->min.x+pos.x, rect->min.y+pos.y, rect->w(), rect->h());
  400. }else XMoveResizeWindow(XDisplay, Grab, -1, -1, 1, 1); // move outside of desktop area
  401. XFlush(XDisplay);
  402. XGrabPointer(XDisplay, App.Hwnd(), false, ButtonPressMask|ButtonReleaseMask|EnterWindowMask|LeaveWindowMask, GrabModeAsync, GrabModeAsync, custom ? Grab : App.Hwnd(), NULL, CurrentTime);
  403. }
  404. }
  405. #elif WEB
  406. if(rect)emscripten_request_pointerlock(null, true);
  407. else emscripten_exit_pointerlock ();
  408. #endif
  409. }
  410. void Mouse::clipUpdate()
  411. {
  412. if(App.active() && (_freezed || _clip_rect_on || _clip_window))
  413. {
  414. RectI recti, window_rect;
  415. if(_clip_window)
  416. {
  417. window_rect=D.screenToWindowPixelI(D.rect()); // usually this is (0, 0)..(D.resW, D.resH) however for VR it's calculated based on its GuiTexture
  418. #if MAC // on Mac having mouse on the window border captures cursor for resizing, and mouse clicks do nothing
  419. if(!D.full())
  420. {
  421. const Int border=3;
  422. window_rect.min.x+=border;
  423. window_rect.max.x-=border;
  424. window_rect.max.y-=border;
  425. }
  426. #endif
  427. }
  428. if(_freezed)
  429. {
  430. Vec2 p =pos(); if(_clip_rect_on)p&=_clip_rect;
  431. VecI2 pi=D.screenToWindowPixelI(p);
  432. if(_clip_window)pi&=window_rect;
  433. recti=pi;
  434. }else
  435. if(_clip_rect_on)
  436. {
  437. recti=D.screenToWindowPixelI(_clip_rect);
  438. if(_clip_window)
  439. { // intersect min/max separately to make sure 'recti' isn't invalid, doing just "recti&=window_rect" could result in invalid rectangle and system call could be ignored
  440. recti.min&=window_rect;
  441. recti.max&=window_rect;
  442. }
  443. }else // window
  444. {
  445. recti=window_rect;
  446. }
  447. #if !WINDOWS_NEW // can't do this for WINDOWS_NEW because we need 'recti' to be inclusive
  448. recti.max++;
  449. #endif
  450. Clip(&recti);
  451. }else Clip(null);
  452. }
  453. Mouse& Mouse::clip(C Rect *rect, Int window)
  454. {
  455. Bool rect_on=(rect!=null),
  456. win =((window<0) ? _clip_window : (window!=0)); // <0 - keep old, >=0 - set new
  457. if(_clip_rect_on!=rect_on || (rect_on && _clip_rect!=*rect) || _clip_window!=win) // if something changes
  458. {
  459. if(_clip_rect_on=rect_on)_clip_rect=*rect;
  460. _clip_window=win;
  461. clipUpdate();
  462. }
  463. return T;
  464. }
  465. Mouse& Mouse::freeze() {_freeze=true; return T;}
  466. /******************************************************************************/
  467. #if WINDOWS_NEW
  468. static void MouseResetVisibility() {Ms.resetVisibility();}
  469. #endif
  470. void Mouse::resetVisibility()
  471. {
  472. #if WINDOWS_NEW
  473. if(!App.mainThread()){App._callbacks.include(MouseResetVisibility); return;} // for Windows New this can be called only on the main thread
  474. #endif
  475. Int cur; // -1=system default, 0=hidden, 1=custom hardware
  476. if(!App.active()
  477. #if !WINDOWS_NEW
  478. || !_on_client
  479. #endif
  480. )cur=-1;else // for example: not on the window
  481. if(hidden())cur=0;else // for example: want to be hidden
  482. if(MsCur.is())cur=1;else // for example: our own custom hardware cursor
  483. if(_image)cur=0;else // for example: our own custom non-hardware cursor
  484. cur=-1; // use default
  485. #if WINDOWS_OLD
  486. SetCursor((cur<0) ? LoadCursor(null, IDC_ARROW) : (cur==0) ? null : MsCur._cursor);
  487. #elif WINDOWS_NEW
  488. if(App.hwnd())
  489. {
  490. App.Hwnd()->PointerCursor=((cur<0) ? ref new CoreCursor(CoreCursorType::Arrow, 0) : (cur==0) ? null : MsCur._cursor);
  491. _locked=(App.Hwnd()->PointerCursor==null);
  492. }
  493. #elif MAC
  494. if(cur)[((cur>0) ? MsCur._cursor : [NSCursor arrowCursor]) set];
  495. Bool visible=(cur!=0); if(visible!=CGCursorIsVisible())if(visible)[NSCursor unhide];else [NSCursor hide];
  496. #elif LINUX
  497. if(XDisplay && App.hwnd())XDefineCursor(XDisplay, App.Hwnd(), (cur<0) ? NULL : (cur==0) ? MsCurEmpty._cursor : MsCur._cursor);
  498. #endif
  499. }
  500. void Mouse::resetCursor()
  501. {
  502. Bool hardware=(_want_cur_hw && !VR.active()); // can't use hardware cursor in VR mode (it can be enabled there, however that would also require drawing it manually on the gui surface, and that would result in cursor being drawn twice on the window - 1-on gui surface 2-using hardware cursor)
  503. if(hardware && _image)MsCur.create(*_image, _hot_spot);
  504. else MsCur.del();
  505. resetVisibility();
  506. }
  507. Mouse& Mouse::cursor(C ImagePtr &image, C VecI2 &hot_spot, Bool hardware, Bool reset)
  508. {
  509. if(T._image!=image || _want_cur_hw!=hardware || T._hot_spot!=hot_spot || reset)
  510. {
  511. T._image =image;
  512. T._hot_spot =hot_spot;
  513. T._want_cur_hw=hardware;
  514. resetCursor();
  515. }
  516. return T;
  517. }
  518. /******************************************************************************/
  519. Mouse& Mouse::visible(Bool show)
  520. {
  521. if(_visible!=show)
  522. {
  523. _visible=show;
  524. resetVisibility();
  525. }
  526. return T;
  527. }
  528. /******************************************************************************/
  529. void Mouse::eat(Int b)
  530. {
  531. if(InRange(b, _button))FlagDisable(_button[b], BS_NOT_ON);
  532. }
  533. void Mouse::eatWheel() {_wheel.zero(); _wheel_i.zero();} // don't clear '_wheel_f' because it should continue to be accumulated
  534. void Mouse::eat () {REPA(_button)eat(i);}
  535. /******************************************************************************/
  536. void Mouse::acquire(Bool on)
  537. {
  538. #if WINDOWS_OLD
  539. #if !MS_RAW_INPUT
  540. if(MOUSE_MODE==FOREGROUND && _did){if(on)_did->Acquire();else _did->Unacquire();} // we need to change acquire only if we're operating in Foreground mode
  541. #endif
  542. #if SET_HOOK
  543. if(on)SetHook();else UnHook();
  544. #endif
  545. #endif
  546. clipUpdate();
  547. resetVisibility();
  548. }
  549. /******************************************************************************/
  550. void Mouse::clear()
  551. {
  552. eatWheel();
  553. _delta_relative.zero();
  554. _deltai .zero();
  555. REPAO(_button)&=~BS_NOT_ON;
  556. if(DelayPush){DelayPush=false; push(0);}
  557. }
  558. /******************************************************************************/
  559. void Mouse::push(Byte b, Flt double_click_time)
  560. {
  561. if(InRange(b, _button) && !(_button[b]&BS_ON))
  562. {
  563. if(br(b) && !b) // if the button was released in the same frame, then don't push it now, but delay for the next frame, so 'tapped' 'tappedFirst' can be properly detected (this is only for first button because of double clicks)
  564. {
  565. DelayPush=true;
  566. }else
  567. {
  568. InputCombo.add(InputButton(INPUT_MOUSE, b));
  569. _button[b]|=BS_PUSHED|BS_ON;
  570. _detected =true;
  571. if(_cur==b && _first && Time.appTime()<=_start_time+double_click_time+Time.ad())
  572. {
  573. _button[b]|=BS_DOUBLE;
  574. _first=false;
  575. }else
  576. {
  577. _first=true;
  578. }
  579. _cur =b;
  580. _start_pos =pos();
  581. _start_time=Time.appTime();
  582. }
  583. }
  584. }
  585. void Mouse::release(Byte b)
  586. {
  587. if(InRange(b, _button) && (_button[b]&BS_ON))
  588. {
  589. FlagDisable(_button[b], BS_ON );
  590. FlagEnable (_button[b], BS_RELEASED);
  591. if(!selecting() && life()<=0.25f+Time.ad())_button[b]|=BS_TAPPED;
  592. }
  593. }
  594. /******************************************************************************/
  595. void Mouse::update()
  596. {
  597. // clip
  598. if(_freeze){if(!_freezed){_freezed=true ; clipUpdate();} _freeze=false;}
  599. else {if( _freezed){_freezed=false; clipUpdate();} }
  600. #if WINDOWS_NEW
  601. if(App.active() && (_freezed || _clip_rect_on || _clip_window))clipUpdate();
  602. #endif
  603. {
  604. #if WINDOWS_OLD
  605. #if !MS_RAW_INPUT
  606. // button state
  607. if(_did)
  608. {
  609. DIMOUSESTATE2 dims; if(OK(_did->GetDeviceState(SIZE(dims), &dims)))
  610. {
  611. _delta_relative.x= dims.lX;
  612. _delta_relative.y=-dims.lY;
  613. }else _did->Acquire(); // try to re-acquire if lost access for some reason
  614. DIDEVICEOBJECTDATA didods[BUF_BUTTONS]; DWORD elms=BUF_BUTTONS; if(OK(_did->GetDeviceData(SIZE(DIDEVICEOBJECTDATA), didods, &elms, 0)))FREP(elms) // process in order
  615. {
  616. ASSERT(DIMOFS_BUTTON0+1==DIMOFS_BUTTON1
  617. && DIMOFS_BUTTON1+1==DIMOFS_BUTTON2
  618. && DIMOFS_BUTTON2+1==DIMOFS_BUTTON3
  619. && DIMOFS_BUTTON3+1==DIMOFS_BUTTON4
  620. && DIMOFS_BUTTON4+1==DIMOFS_BUTTON5
  621. && DIMOFS_BUTTON5+1==DIMOFS_BUTTON6
  622. && DIMOFS_BUTTON6+1==DIMOFS_BUTTON7); // check that macros are continuous
  623. C DIDEVICEOBJECTDATA &didod=didods[i];
  624. Int b=didod.dwOfs-DIMOFS_BUTTON0; if(b>=5 && InRange(b, 8)) // buttons 0..4 are processed in 'WindowMsg'
  625. {
  626. if(didod.dwData&0x80){if(MOUSE_MODE==FOREGROUND || App.active())push(b);}else release(b);
  627. }
  628. }
  629. }
  630. #endif
  631. // need to manually check for releases because WM_*BUTTONUP aren't processed when mouse is outside of client window even when app is still active
  632. REP(Min(Elms(_button), Elms(Keys)))
  633. {
  634. #if 1 // this is faster
  635. if(b(i) && GetKeyState (Keys[i])>=0)release(i);
  636. #else
  637. if(b(i) && GetAsyncKeyState(Keys[i])>=0)release(i);
  638. #endif
  639. }
  640. // position and delta
  641. POINT p; if(GetCursorPos(&p))
  642. {
  643. _deltai.x=p.x-_desktop_posi.x; _desktop_posi.x=p.x;
  644. _deltai.y=p.y-_desktop_posi.y; _desktop_posi.y=p.y;
  645. ScreenToClient(App.Hwnd(), &p);
  646. _window_posi.x=p.x;
  647. _window_posi.y=p.y;
  648. }
  649. _on_client=(InRange(_window_posi.x, D.resW()) && InRange(_window_posi.y, D.resH()) && WindowMouse()==App.hwnd());
  650. // 'resetVisibility' is always called in 'WM_SETCURSOR'
  651. #elif WINDOWS_NEW
  652. if(App.hwnd())
  653. {
  654. VecI2 posi(DipsToPixels(App.Hwnd()->PointerPosition.X), DipsToPixels(App.Hwnd()->PointerPosition.Y));
  655. _deltai =_desktop_posi-posi; // calc based on '_desktop_posi' because '_window_posi' is relative to window position (so if we move the window based on delta issues could happen)
  656. _desktop_posi= posi;
  657. Windows::Foundation::Rect bounds=App.Hwnd()->Bounds;
  658. _window_posi.set(posi.x-DipsToPixels(bounds.X),
  659. posi.y-DipsToPixels(bounds.Y));
  660. // need to check buttons manually, because 'OnPointerPressed' will not catch events for other buttons if one button is already pressed
  661. REP(Min(Elms(_button), Elms(Keys)))
  662. {
  663. #if 1 // this is faster
  664. Int on=((Int)App.Hwnd()->GetKeyState (VirtualKey(Keys[i])) & (Int)CoreVirtualKeyStates::Down);
  665. #else
  666. Int on=((Int)App.Hwnd()->GetAsyncKeyState(VirtualKey(Keys[i])) & (Int)CoreVirtualKeyStates::Down);
  667. #endif
  668. if(!on)release(i);else if(_on_client)push(i); // don't push when clicking on title bar or another system window (but allow releases in that case)
  669. }
  670. }
  671. // '_on_client' is managed through 'OnPointerEntered' and 'OnPointerExited' callbacks
  672. #elif MAC
  673. // '_on_client' is managed through 'mouseEntered' and 'mouseExited' callbacks
  674. VecI2 screen=D.screen();
  675. RectI client=WindowRect(true);
  676. // desktop position
  677. VecI2 old_posi=_desktop_posi;
  678. // apply clipping
  679. if(MouseClipOn)
  680. {
  681. VecI2 cur_pos=_desktop_posi;
  682. RectI clip=MouseClipRect;
  683. clip.min+=client.min; MAX(clip.min.x, 2); MAX(clip.min.y, 2); // padd to don't touch edges in order to avoid popping dock
  684. clip.max+=client.min; MIN(clip.max.x, screen.x-3); MIN(clip.max.y, screen.y-3);
  685. _desktop_posi.x=Round(_desktop_posi.x+_delta_relative.x);
  686. _desktop_posi.y=Round(_desktop_posi.y-_delta_relative.y);
  687. _desktop_posi &=clip;
  688. MouseIgnore+=_desktop_posi-cur_pos;
  689. CGPoint p; p.x=_desktop_posi.x; p.y=_desktop_posi.y;
  690. CGWarpMouseCursorPosition(p);
  691. }else
  692. {
  693. NSPoint p=[NSEvent mouseLocation];
  694. _desktop_posi.x= Round(p.x);
  695. _desktop_posi.y=screen.y-Round(p.y);
  696. }
  697. _deltai=_desktop_posi-old_posi;
  698. // window position
  699. _window_posi=_desktop_posi-client.min;
  700. #elif LINUX
  701. if(XDisplay)
  702. {
  703. int x, y;
  704. unsigned int mask;
  705. XWindow root, child;
  706. XQueryPointer(XDisplay, App.Hwnd(), &root, &child, &x, &y, &_window_posi.x, &_window_posi.y, &mask);
  707. _deltai.x=x-_desktop_posi.x; _desktop_posi.x=x;
  708. _deltai.y=y-_desktop_posi.y; _desktop_posi.y=y;
  709. // '_on_client' is managed through 'EnterNotify' and 'LeaveNotify' events
  710. }
  711. #else
  712. // desktop and win pos obtained externally in main loop
  713. _on_client=(InRange(_window_posi.x, D.resW()) && InRange(_window_posi.y, D.resH()));
  714. #if WEB
  715. _on_client|=_locked;
  716. #endif
  717. #endif
  718. }
  719. Vec2 old=_pos;
  720. _delta_relative*=_speed;
  721. #if WINDOWS_NEW || WEB
  722. if(_locked) // for WINDOWS_NEW and WEB when '_locked', the '_window_posi' never changes so we need to manually adjust the '_pos' based on '_delta_relative'
  723. {
  724. if(!_freezed)_pos+=_delta_relative*(D.size().max()*0.7f); // make movement speed dependent on the screen size
  725. // clip
  726. if(_clip_rect_on)
  727. {
  728. Clamp(_pos.x, _clip_rect.min.x, _clip_rect.max.x);
  729. Clamp(_pos.y, _clip_rect.min.y, _clip_rect.max.y);
  730. }else
  731. {
  732. Clamp(_pos.x, -D.w(), D.w());
  733. Clamp(_pos.y, -D.h(), D.h());
  734. }
  735. }else
  736. #endif
  737. {
  738. _pos=D.windowPixelToScreen(_window_posi);
  739. }
  740. _delta_clp=_pos-old;
  741. _delta =_sv_delta.update(_delta_relative); // yes, mouse delta smoothing is needed, especially for low fps (for example ~40), without this, player camera rotation was not smooth
  742. if(Time.ad())_vel =_sv_vel .update(_delta_clp/Time.ad(), Time.ad()); // use '_delta_clp' to match exact cursor position
  743. // dragging
  744. if(b(_cur))
  745. {
  746. if(!selecting() && Dist2(pos(), startPos())*Sqr(D.scale())>=SELECT_DIST_2 )_selecting=true; // skip 'D.smallSize' because mouse input is independent on screen size
  747. if(!dragging () && selecting() && life()>=DragTime+Time.ad())_dragging =true;
  748. }else
  749. if(!br(_cur))_dragging=_selecting=false; // disable dragging only if button not on and not released, to allow detection inside the game for "release after dragging"
  750. // wheel
  751. if(_wheel.any())
  752. {
  753. if(Time.appTime()>_wheel_time+1)_wheel_f=_wheel;else _wheel_f+=_wheel; // if enough time passed without any wheel movement then start from scratch, otherwise keep accumulating
  754. _wheel_time=Time.appTime(); // remember current app time
  755. if(Int i=RoundEps(_wheel_f.x, 0.1f)){_wheel_i.x+=i; _wheel_f.x-=i;} // increase integer counters if enough movement occurred, use custom epsilon instead of 0.5f, we need it >0 to make sure that for example 2.999f will get converted to 3
  756. if(Int i=RoundEps(_wheel_f.y, 0.1f)){_wheel_i.y+=i; _wheel_f.y-=i;} // increase integer counters if enough movement occurred, use custom epsilon instead of 0.5f, we need it >0 to make sure that for example 2.999f will get converted to 3
  757. }
  758. // action
  759. _action =(pixelDelta().any() || _wheel.any() || bp(0));
  760. _detected|=_action;
  761. }
  762. /******************************************************************************/
  763. void Mouse::draw()
  764. {
  765. if(_image && _detected && visible() && !MsCur.is() && _on_client
  766. #if !WINDOWS_OLD
  767. && App.active()
  768. #endif
  769. )
  770. {
  771. Vec2 pos=D.screenAlignedToPixel(T.pos());
  772. if(_hot_spot.any())
  773. {
  774. Vec2 size=D.pixelToScreenSize(_hot_spot);
  775. pos.x-=size.x;
  776. pos.y+=size.y;
  777. }
  778. _image->draw(Rect_LU(pos, D.pixelToScreenSize(_image->size())));
  779. }
  780. }
  781. /******************************************************************************/
  782. }
  783. /******************************************************************************/