Blender中的事件处理器
在Blender中,事件主要指以下内容(参见:https://wiki.blender.org/index.php/Dev:2.5/Source/Architecture/Window_Manager)
- 键盘、鼠标、设备、计时器
- custom data (tablet, drag n drop)
- modifier state, mouse coords
- key modifiers (LMB, H-key, etc)
- modifier order (Ctrl+Alt or Alt+Ctrl)
- gestures (border, lasso, lines, etc)
- multi-event (H, J, K etc)
事件处理是交由GHOST系统来完成的,在主程序中主事件循环代码在WM_Main(wm.c,bf_windowmanager库)函数中:
1 void WM_main(bContext *C) 2 { 3 /* Single refresh before handling events. 4 * This ensures we don't run operators before the depsgraph has been evaluated. */ 5 wm_event_do_refresh_wm_and_depsgraph(C); 6 7 while (1) { 8 9 /* get events from ghost, handle window events, add to window queues */ 10 wm_window_process_events(C); 11 12 /* per window, all events to the window, screen, area and region handlers */ 13 wm_event_do_handlers(C); 14 15 /* events have left notes about changes, we handle and cache it */ 16 wm_event_do_notifiers(C); 17 18 /* execute cached changes draw */ 19 wm_draw_update(C); 20 } 21 }
主循环中四步分别处理四种数据:event(事件)、operator(操作处理器)、notifier(通知)、draw(绘制)。
我们关心事件处理,也就是第10行代码完成的,它调用wm_window_process_events函数:
1 void wm_window_process_events(const bContext *C) 2 { 3 int hasevent; 4 5 BLI_assert(BLI_thread_is_main()); 6 7 hasevent = GHOST_ProcessEvents(g_system, 0); /* 0 is no wait */ 8 9 if (hasevent) 10 GHOST_DispatchEvents(g_system); 11 12 hasevent |= wm_window_timer(C); 13 14 /* no event, we sleep 5 milliseconds */ 15 if (hasevent == 0) 16 PIL_sleep_ms(5); 17 }
这个函数很简单,从操作系统中取得消息事件,有事件则进行派发,完成事件的处理过程。我们具体来看看这二个过程。这二个过程位于GHOST_C-api.cpp文件中,属于bf_intern_ghost库
1 int GHOST_ProcessEvents(GHOST_SystemHandle systemhandle, int waitForEvent) 2 { 3 GHOST_ISystem *system = (GHOST_ISystem *) systemhandle; 4 5 return (int) system->processEvents(waitForEvent ? true : false); 6 } 7 8 9 10 void GHOST_DispatchEvents(GHOST_SystemHandle systemhandle) 11 { 12 GHOST_ISystem *system = (GHOST_ISystem *) systemhandle; 13 14 system->dispatchEvents(); 15 }
由于ghost库是使用c++编译的,而bf_windowmanager库是c编译的,为了方便c代码中使用c++代码,所有ghost对外接口的代码都在GHOST_C-api.cpp中进行了包装,这二个函数也是这个目的,它们分别于5和14行调用了GHOST系统的processEvents和dispathcEvent函数。processEvents函数与操作系统密切相关的,在window操作系统中对应是GHOST_SystemWin32.cpp中函数:
1 bool GHOST_SystemWin32::processEvents(bool waitForEvent) 2 { 3 MSG msg; 4 bool anyProcessed = false; 5 6 do { 7 GHOST_TimerManager *timerMgr = getTimerManager(); 8 9 if (waitForEvent && !::PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE)) { 10 #if 1 11 ::Sleep(1); 12 #else 13 GHOST_TUns64 next = timerMgr->nextFireTime(); 14 GHOST_TInt64 maxSleep = next - getMilliSeconds(); 15 16 if (next == GHOST_kFireTimeNever) { 17 ::WaitMessage(); 18 } 19 else if (maxSleep >= 0.0) { 20 ::SetTimer(NULL, 0, maxSleep, NULL); 21 ::WaitMessage(); 22 ::KillTimer(NULL, 0); 23 } 24 #endif 25 } 26 27 if (timerMgr->fireTimers(getMilliSeconds())) { 28 anyProcessed = true; 29 } 30 31 // Process all the events waiting for us 32 while (::PeekMessageW(&msg, 0, 0, 0, PM_REMOVE) != 0) { 33 // TranslateMessage doesn't alter the message, and doesn't change our raw keyboard data. 34 // Needed for MapVirtualKey or if we ever need to get chars from wm_ime_char or similar. 35 ::TranslateMessage(&msg); 36 ::DispatchMessageW(&msg); 37 anyProcessed = true; 38 } 39 } while (waitForEvent && !anyProcessed); 40 41 return anyProcessed; 42 }
32行开始的循环调用操作系统的api处理消息事件,windows操作系统将消息派发到窗口过程中。Blender在windows系统中的窗口回调过程也位于GHOST_SystemWin32.cpp中:
1 LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 2 { 3 GHOST_Event *event = NULL; 4 bool eventHandled = false; 5 6 LRESULT lResult = 0; 7 GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem(); 8 GHOST_EventManager *eventManager = system->getEventManager(); 9 GHOST_ASSERT(system, "GHOST_SystemWin32::s_wndProc(): system not initialized"); 10 11 if (hwnd) { 12 #if 0 13 // Disabled due to bug in Intel drivers, see T51959 14 if(msg == WM_NCCREATE) { 15 // Tell Windows to automatically handle scaling of non-client areas 16 // such as the caption bar. EnableNonClientDpiScaling was introduced in Windows 10 17 HMODULE m_user32 = ::LoadLibrary("User32.dll"); 18 if (m_user32) { 19 GHOST_WIN32_EnableNonClientDpiScaling fpEnableNonClientDpiScaling = 20 (GHOST_WIN32_EnableNonClientDpiScaling) ::GetProcAddress(m_user32, "EnableNonClientDpiScaling"); 21 22 if (fpEnableNonClientDpiScaling) { 23 fpEnableNonClientDpiScaling(hwnd); 24 } 25 } 26 } 27 #endif 28 29 GHOST_WindowWin32 *window = (GHOST_WindowWin32 *)::GetWindowLongPtr(hwnd, GWLP_USERDATA); 30 if (window) { 31 switch (msg) { 32 // we need to check if new key layout has AltGr 33 case WM_INPUTLANGCHANGE: 34 { 35 system->handleKeyboardChange(); 36 #ifdef WITH_INPUT_IME 37 window->getImeInput()->SetInputLanguage(); 38 #endif 39 break; 40 } 41 //////////////////////////////////////////////////////////////////////// 42 // Keyboard events, processed 43 //////////////////////////////////////////////////////////////////////// 44 case WM_INPUT: 45 { 46 // check WM_INPUT from input sink when ghost window is not in the foreground 47 if (wParam == RIM_INPUTSINK) { 48 if (GetFocus() != hwnd) // WM_INPUT message not for this window 49 return 0; 50 } //else wParam == RIM_INPUT 51 52 RAWINPUT raw; 53 RAWINPUT *raw_ptr = &raw; 54 UINT rawSize = sizeof(RAWINPUT); 55 56 GetRawInputData((HRAWINPUT)lParam, RID_INPUT, raw_ptr, &rawSize, sizeof(RAWINPUTHEADER)); 57 58 switch (raw.header.dwType) { 59 case RIM_TYPEKEYBOARD: 60 event = processKeyEvent(window, raw); 61 if (!event) { 62 GHOST_PRINT("GHOST_SystemWin32::wndProc: key event "); 63 GHOST_PRINT(msg); 64 GHOST_PRINT(" key ignored\n"); 65 } 66 break; 67 #ifdef WITH_INPUT_NDOF 68 case RIM_TYPEHID: 69 if (system->processNDOF(raw)) 70 eventHandled = true; 71 break; 72 #endif 73 } 74 break; 75 } 76 #ifdef WITH_INPUT_IME 77 //////////////////////////////////////////////////////////////////////// 78 // IME events, processed, read more in GHOST_IME.h 79 //////////////////////////////////////////////////////////////////////// 80 case WM_IME_SETCONTEXT: 81 { 82 GHOST_ImeWin32 *ime = window->getImeInput(); 83 ime->SetInputLanguage(); 84 ime->CreateImeWindow(hwnd); 85 ime->CleanupComposition(hwnd); 86 ime->CheckFirst(hwnd); 87 break; 88 } 89 case WM_IME_STARTCOMPOSITION: 90 { 91 GHOST_ImeWin32 *ime = window->getImeInput(); 92 eventHandled = true; 93 /* remove input event before start comp event, avoid redundant input */ 94 eventManager->removeTypeEvents(GHOST_kEventKeyDown, window); 95 ime->CreateImeWindow(hwnd); 96 ime->ResetComposition(hwnd); 97 event = processImeEvent( 98 GHOST_kEventImeCompositionStart, 99 window, 100 &ime->eventImeData); 101 break; 102 } 103 case WM_IME_COMPOSITION: 104 { 105 GHOST_ImeWin32 *ime = window->getImeInput(); 106 eventHandled = true; 107 ime->UpdateImeWindow(hwnd); 108 ime->UpdateInfo(hwnd); 109 if (ime->eventImeData.result_len) { 110 /* remove redundant IME event */ 111 eventManager->removeTypeEvents(GHOST_kEventImeComposition, window); 112 } 113 event = processImeEvent( 114 GHOST_kEventImeComposition, 115 window, 116 &ime->eventImeData); 117 break; 118 } 119 case WM_IME_ENDCOMPOSITION: 120 { 121 GHOST_ImeWin32 *ime = window->getImeInput(); 122 eventHandled = true; 123 /* remove input event after end comp event, avoid redundant input */ 124 eventManager->removeTypeEvents(GHOST_kEventKeyDown, window); 125 ime->ResetComposition(hwnd); 126 ime->DestroyImeWindow(hwnd); 127 event = processImeEvent( 128 GHOST_kEventImeCompositionEnd, 129 window, 130 &ime->eventImeData); 131 break; 132 } 133 #endif /* WITH_INPUT_IME */ 134 //////////////////////////////////////////////////////////////////////// 135 // Keyboard events, ignored 136 //////////////////////////////////////////////////////////////////////// 137 case WM_KEYDOWN: 138 case WM_SYSKEYDOWN: 139 case WM_KEYUP: 140 case WM_SYSKEYUP: 141 /* These functions were replaced by WM_INPUT*/ 142 case WM_CHAR: 143 /* The WM_CHAR message is posted to the window with the keyboard focus when 144 * a WM_KEYDOWN message is translated by the TranslateMessage function. WM_CHAR 145 * contains the character code of the key that was pressed. 146 */ 147 case WM_DEADCHAR: 148 /* The WM_DEADCHAR message is posted to the window with the keyboard focus when a 149 * WM_KEYUP message is translated by the TranslateMessage function. WM_DEADCHAR 150 * specifies a character code generated by a dead key. A dead key is a key that 151 * generates a character, such as the umlaut (double-dot), that is combined with 152 * another character to form a composite character. For example, the umlaut-O 153 * character (Ö) is generated by typing the dead key for the umlaut character, and 154 * then typing the O key. 155 */ 156 break; 157 case WM_SYSDEADCHAR: 158 /* The WM_SYSDEADCHAR message is sent to the window with the keyboard focus when 159 * a WM_SYSKEYDOWN message is translated by the TranslateMessage function. 160 * WM_SYSDEADCHAR specifies the character code of a system dead key - that is, 161 * a dead key that is pressed while holding down the alt key. 162 */ 163 case WM_SYSCHAR: 164 /* The WM_SYSCHAR message is sent to the window with the keyboard focus when 165 * a WM_SYSCHAR message is translated by the TranslateMessage function. 166 * WM_SYSCHAR specifies the character code of a dead key - that is, 167 * a dead key that is pressed while holding down the alt key. 168 * To prevent the sound, DefWindowProc must be avoided by return 169 */ 170 break; 171 case WM_SYSCOMMAND: 172 /* The WM_SYSCHAR message is sent to the window when system commands such as 173 * maximize, minimize or close the window are triggered. Also it is sent when ALT 174 * button is press for menu. To prevent this we must return preventing DefWindowProc. 175 */ 176 if (wParam == SC_KEYMENU) { 177 eventHandled = true; 178 } 179 break; 180 //////////////////////////////////////////////////////////////////////// 181 // Tablet events, processed 182 //////////////////////////////////////////////////////////////////////// 183 case WT_PACKET: 184 window->processWin32TabletEvent(wParam, lParam); 185 break; 186 case WT_CSRCHANGE: 187 case WT_PROXIMITY: 188 window->processWin32TabletInitEvent(); 189 break; 190 //////////////////////////////////////////////////////////////////////// 191 // Mouse events, processed 192 //////////////////////////////////////////////////////////////////////// 193 case WM_LBUTTONDOWN: 194 window->registerMouseClickEvent(0); 195 event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskLeft); 196 break; 197 case WM_MBUTTONDOWN: 198 window->registerMouseClickEvent(0); 199 event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskMiddle); 200 break; 201 case WM_RBUTTONDOWN: 202 window->registerMouseClickEvent(0); 203 event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskRight); 204 break; 205 case WM_XBUTTONDOWN: 206 window->registerMouseClickEvent(0); 207 if ((short) HIWORD(wParam) == XBUTTON1) { 208 event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskButton4); 209 } 210 else if ((short) HIWORD(wParam) == XBUTTON2) { 211 event = processButtonEvent(GHOST_kEventButtonDown, window, GHOST_kButtonMaskButton5); 212 } 213 break; 214 case WM_LBUTTONUP: 215 window->registerMouseClickEvent(1); 216 event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskLeft); 217 break; 218 case WM_MBUTTONUP: 219 window->registerMouseClickEvent(1); 220 event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskMiddle); 221 break; 222 case WM_RBUTTONUP: 223 window->registerMouseClickEvent(1); 224 event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskRight); 225 break; 226 case WM_XBUTTONUP: 227 window->registerMouseClickEvent(1); 228 if ((short) HIWORD(wParam) == XBUTTON1) { 229 event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskButton4); 230 } 231 else if ((short) HIWORD(wParam) == XBUTTON2) { 232 event = processButtonEvent(GHOST_kEventButtonUp, window, GHOST_kButtonMaskButton5); 233 } 234 break; 235 case WM_MOUSEMOVE: 236 event = processCursorEvent(GHOST_kEventCursorMove, window); 237 break; 238 case WM_MOUSEWHEEL: 239 { 240 /* The WM_MOUSEWHEEL message is sent to the focus window 241 * when the mouse wheel is rotated. The DefWindowProc 242 * function propagates the message to the window's parent. 243 * There should be no internal forwarding of the message, 244 * since DefWindowProc propagates it up the parent chain 245 * until it finds a window that processes it. 246 */ 247 248 /* Get the window under the mouse and send event to its queue. */ 249 POINT mouse_pos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; 250 HWND mouse_hwnd = ChildWindowFromPoint(HWND_DESKTOP, mouse_pos); 251 GHOST_WindowWin32 *mouse_window = (GHOST_WindowWin32 *)::GetWindowLongPtr(mouse_hwnd, GWLP_USERDATA); 252 253 processWheelEvent(mouse_window ? mouse_window : window , wParam, lParam); 254 eventHandled = true; 255 #ifdef BROKEN_PEEK_TOUCHPAD 256 PostMessage(hwnd, WM_USER, 0, 0); 257 #endif 258 break; 259 } 260 case WM_SETCURSOR: 261 /* The WM_SETCURSOR message is sent to a window if the mouse causes the cursor 262 * to move within a window and mouse input is not captured. 263 * This means we have to set the cursor shape every time the mouse moves! 264 * The DefWindowProc function uses this message to set the cursor to an 265 * arrow if it is not in the client area. 266 */ 267 if (LOWORD(lParam) == HTCLIENT) { 268 // Load the current cursor 269 window->loadCursor(window->getCursorVisibility(), window->getCursorShape()); 270 // Bypass call to DefWindowProc 271 return 0; 272 } 273 else { 274 // Outside of client area show standard cursor 275 window->loadCursor(true, GHOST_kStandardCursorDefault); 276 } 277 break; 278 279 //////////////////////////////////////////////////////////////////////// 280 // Mouse events, ignored 281 //////////////////////////////////////////////////////////////////////// 282 case WM_NCMOUSEMOVE: 283 /* The WM_NCMOUSEMOVE message is posted to a window when the cursor is moved 284 * within the nonclient area of the window. This message is posted to the window 285 * that contains the cursor. If a window has captured the mouse, this message is not posted. 286 */ 287 case WM_NCHITTEST: 288 /* The WM_NCHITTEST message is sent to a window when the cursor moves, or 289 * when a mouse button is pressed or released. If the mouse is not captured, 290 * the message is sent to the window beneath the cursor. Otherwise, the message 291 * is sent to the window that has captured the mouse. 292 */ 293 break; 294 295 //////////////////////////////////////////////////////////////////////// 296 // Window events, processed 297 //////////////////////////////////////////////////////////////////////// 298 case WM_CLOSE: 299 /* The WM_CLOSE message is sent as a signal that a window or an application should terminate. */ 300 event = processWindowEvent(GHOST_kEventWindowClose, window); 301 break; 302 case WM_ACTIVATE: 303 /* The WM_ACTIVATE message is sent to both the window being activated and the window being 304 * deactivated. If the windows use the same input queue, the message is sent synchronously, 305 * first to the window procedure of the top-level window being deactivated, then to the window 306 * procedure of the top-level window being activated. If the windows use different input queues, 307 * the message is sent asynchronously, so the window is activated immediately. 308 */ 309 { 310 GHOST_ModifierKeys modifiers; 311 modifiers.clear(); 312 system->storeModifierKeys(modifiers); 313 system->m_wheelDeltaAccum = 0; 314 event = processWindowEvent(LOWORD(wParam) ? GHOST_kEventWindowActivate : GHOST_kEventWindowDeactivate, window); 315 /* WARNING: Let DefWindowProc handle WM_ACTIVATE, otherwise WM_MOUSEWHEEL 316 * will not be dispatched to OUR active window if we minimize one of OUR windows. */ 317 if (LOWORD(wParam) == WA_INACTIVE) 318 window->lostMouseCapture(); 319 320 lResult = ::DefWindowProc(hwnd, msg, wParam, lParam); 321 break; 322 } 323 case WM_ENTERSIZEMOVE: 324 /* The WM_ENTERSIZEMOVE message is sent one time to a window after it enters the moving 325 * or sizing modal loop. The window enters the moving or sizing modal loop when the user 326 * clicks the window's title bar or sizing border, or when the window passes the 327 * WM_SYSCOMMAND message to the DefWindowProc function and the wParam parameter of the 328 * message specifies the SC_MOVE or SC_SIZE value. The operation is complete when 329 * DefWindowProc returns. 330 */ 331 window->m_inLiveResize = 1; 332 break; 333 case WM_EXITSIZEMOVE: 334 window->m_inLiveResize = 0; 335 break; 336 case WM_PAINT: 337 /* An application sends the WM_PAINT message when the system or another application 338 * makes a request to paint a portion of an application's window. The message is sent 339 * when the UpdateWindow or RedrawWindow function is called, or by the DispatchMessage 340 * function when the application obtains a WM_PAINT message by using the GetMessage or 341 * PeekMessage function. 342 */ 343 if (!window->m_inLiveResize) { 344 event = processWindowEvent(GHOST_kEventWindowUpdate, window); 345 ::ValidateRect(hwnd, NULL); 346 } 347 else { 348 eventHandled = true; 349 } 350 break; 351 case WM_GETMINMAXINFO: 352 /* The WM_GETMINMAXINFO message is sent to a window when the size or 353 * position of the window is about to change. An application can use 354 * this message to override the window's default maximized size and 355 * position, or its default minimum or maximum tracking size. 356 */ 357 processMinMaxInfo((MINMAXINFO *) lParam); 358 /* Let DefWindowProc handle it. */ 359 break; 360 case WM_SIZING: 361 case WM_SIZE: 362 /* The WM_SIZE message is sent to a window after its size has changed. 363 * The WM_SIZE and WM_MOVE messages are not sent if an application handles the 364 * WM_WINDOWPOSCHANGED message without calling DefWindowProc. It is more efficient 365 * to perform any move or size change processing during the WM_WINDOWPOSCHANGED 366 * message without calling DefWindowProc. 367 */ 368 /* we get first WM_SIZE before we fully init. So, do not dispatch before we continiously resizng */ 369 if (window->m_inLiveResize) { 370 system->pushEvent(processWindowEvent(GHOST_kEventWindowSize, window)); 371 system->dispatchEvents(); 372 } 373 else { 374 event = processWindowEvent(GHOST_kEventWindowSize, window); 375 } 376 break; 377 case WM_CAPTURECHANGED: 378 window->lostMouseCapture(); 379 break; 380 case WM_MOVING: 381 /* The WM_MOVING message is sent to a window that the user is moving. By processing 382 * this message, an application can monitor the size and position of the drag rectangle 383 * and, if needed, change its size or position. 384 */ 385 case WM_MOVE: 386 /* The WM_SIZE and WM_MOVE messages are not sent if an application handles the 387 * WM_WINDOWPOSCHANGED message without calling DefWindowProc. It is more efficient 388 * to perform any move or size change processing during the WM_WINDOWPOSCHANGED 389 * message without calling DefWindowProc. 390 */ 391 /* see WM_SIZE comment*/ 392 if (window->m_inLiveResize) { 393 system->pushEvent(processWindowEvent(GHOST_kEventWindowMove, window)); 394 system->dispatchEvents(); 395 } 396 else { 397 event = processWindowEvent(GHOST_kEventWindowMove, window); 398 } 399 400 break; 401 case WM_DPICHANGED: 402 /* The WM_DPICHANGED message is sent when the effective dots per inch (dpi) for a window has changed. 403 * The DPI is the scale factor for a window. There are multiple events that can cause the DPI to 404 * change such as when the window is moved to a monitor with a different DPI. 405 */ 406 { 407 WORD newYAxisDPI = HIWORD(wParam); 408 WORD newXAxisDPI = LOWORD(wParam); 409 // The suggested new size and position of the window. 410 RECT* const suggestedWindowRect = (RECT*)lParam; 411 412 // Push DPI change event first 413 system->pushEvent(processWindowEvent(GHOST_kEventWindowDPIHintChanged, window)); 414 system->dispatchEvents(); 415 eventHandled = true; 416 417 // Then move and resize window 418 SetWindowPos(hwnd, 419 NULL, 420 suggestedWindowRect->left, 421 suggestedWindowRect->top, 422 suggestedWindowRect->right - suggestedWindowRect->left, 423 suggestedWindowRect->bottom - suggestedWindowRect->top, 424 SWP_NOZORDER | SWP_NOACTIVATE); 425 } 426 break; 427 //////////////////////////////////////////////////////////////////////// 428 // Window events, ignored 429 //////////////////////////////////////////////////////////////////////// 430 case WM_WINDOWPOSCHANGED: 431 /* The WM_WINDOWPOSCHANGED message is sent to a window whose size, position, or place 432 * in the Z order has changed as a result of a call to the SetWindowPos function or 433 * another window-management function. 434 * The WM_SIZE and WM_MOVE messages are not sent if an application handles the 435 * WM_WINDOWPOSCHANGED message without calling DefWindowProc. It is more efficient 436 * to perform any move or size change processing during the WM_WINDOWPOSCHANGED 437 * message without calling DefWindowProc. 438 */ 439 case WM_ERASEBKGND: 440 /* An application sends the WM_ERASEBKGND message when the window background must be 441 * erased (for example, when a window is resized). The message is sent to prepare an 442 * invalidated portion of a window for painting. 443 */ 444 case WM_NCPAINT: 445 /* An application sends the WM_NCPAINT message to a window when its frame must be painted. */ 446 case WM_NCACTIVATE: 447 /* The WM_NCACTIVATE message is sent to a window when its nonclient area needs to be changed 448 * to indicate an active or inactive state. 449 */ 450 case WM_DESTROY: 451 /* The WM_DESTROY message is sent when a window is being destroyed. It is sent to the window 452 * procedure of the window being destroyed after the window is removed from the screen. 453 * This message is sent first to the window being destroyed and then to the child windows 454 * (if any) as they are destroyed. During the processing of the message, it can be assumed 455 * that all child windows still exist. 456 */ 457 case WM_NCDESTROY: 458 /* The WM_NCDESTROY message informs a window that its nonclient area is being destroyed. The 459 * DestroyWindow function sends the WM_NCDESTROY message to the window following the WM_DESTROY 460 * message. WM_DESTROY is used to free the allocated memory object associated with the window. 461 */ 462 break; 463 case WM_KILLFOCUS: 464 /* The WM_KILLFOCUS message is sent to a window immediately before it loses the keyboard focus. 465 * We want to prevent this if a window is still active and it loses focus to nowhere*/ 466 if (!wParam && hwnd == ::GetActiveWindow()) 467 ::SetFocus(hwnd); 468 case WM_SHOWWINDOW: 469 /* The WM_SHOWWINDOW message is sent to a window when the window is about to be hidden or shown. */ 470 case WM_WINDOWPOSCHANGING: 471 /* The WM_WINDOWPOSCHANGING message is sent to a window whose size, position, or place in 472 * the Z order is about to change as a result of a call to the SetWindowPos function or 473 * another window-management function. 474 */ 475 case WM_SETFOCUS: 476 /* The WM_SETFOCUS message is sent to a window after it has gained the keyboard focus. */ 477 break; 478 //////////////////////////////////////////////////////////////////////// 479 // Other events 480 //////////////////////////////////////////////////////////////////////// 481 case WM_GETTEXT: 482 /* An application sends a WM_GETTEXT message to copy the text that 483 * corresponds to a window into a buffer provided by the caller. 484 */ 485 case WM_ACTIVATEAPP: 486 /* The WM_ACTIVATEAPP message is sent when a window belonging to a 487 * different application than the active window is about to be activated. 488 * The message is sent to the application whose window is being activated 489 * and to the application whose window is being deactivated. 490 */ 491 case WM_TIMER: 492 /* The WIN32 docs say: 493 * The WM_TIMER message is posted to the installing thread's message queue 494 * when a timer expires. You can process the message by providing a WM_TIMER 495 * case in the window procedure. Otherwise, the default window procedure will 496 * call the TimerProc callback function specified in the call to the SetTimer 497 * function used to install the timer. 498 * 499 * In GHOST, we let DefWindowProc call the timer callback. 500 */ 501 break; 502 503 } 504 } 505 else { 506 // Event found for a window before the pointer to the class has been set. 507 GHOST_PRINT("GHOST_SystemWin32::wndProc: GHOST window event before creation\n"); 508 /* These are events we typically miss at this point: 509 * WM_GETMINMAXINFO 0x24 510 * WM_NCCREATE 0x81 511 * WM_NCCALCSIZE 0x83 512 * WM_CREATE 0x01 513 * We let DefWindowProc do the work. 514 */ 515 } 516 } 517 else { 518 // Events without valid hwnd 519 GHOST_PRINT("GHOST_SystemWin32::wndProc: event without window\n"); 520 } 521 522 if (event) { 523 system->pushEvent(event); 524 eventHandled = true; 525 } 526 527 if (!eventHandled) 528 lResult = ::DefWindowProcW(hwnd, msg, wParam, lParam); 529 530 return lResult; 531 }
在522行中,通过前面的处理,将包装成GHOST_Event *类型的event放入事件管理器中:
1 GHOST_TSuccess GHOST_System::pushEvent(GHOST_IEvent *event) 2 { 3 GHOST_TSuccess success; 4 if (m_eventManager) { 5 success = m_eventManager->pushEvent(event); 6 } 7 else { 8 success = GHOST_kFailure; 9 } 10 return success; 11 }
第5行将event推入GHOST_EventManager*类型的m_eventManager中。
/**
* Manages an event stack and a list of event consumers.
* The stack works on a FIFO (First In First Out) basis.
* Events are pushed on the front of the stack and retrieved from the back.
* Ownership of the event is transferred to the event manager as soon as an event is pushed.
* Ownership of the event is transferred from the event manager as soon as an event is popped.
* Events can be dispatched to the event consumers.
*/
class GHOST_EventManager
至此,完成了从windwos系统中获取应用程序消息事件、进一步加工成Blender中的GHOST_Event对象,最后将对象推入GHOST_EventManager对象。当然并不是所有的消息事件都加工成GHOST_Event,只是有需要的才加工的。
这仅完成了主程序事件循环中的wm_window_process_events过程的第一步,下一步就是对事件管理器的GHOST事件进行派发处理过程,它由dispatchEvents完成,位于GHOST_System.cpp中:
1 void GHOST_System::dispatchEvents() 2 { 3 #ifdef WITH_INPUT_NDOF 4 // NDOF Motion event is sent only once per dispatch, so do it now: 5 if (m_ndofManager) { 6 m_ndofManager->sendMotionEvent(); 7 } 8 #endif 9 10 if (m_eventManager) { 11 m_eventManager->dispatchEvents(); 12 } 13 14 m_timerManager->fireTimers(getMilliSeconds()); 15 } 16 17 void GHOST_EventManager::dispatchEvents() 18 { 19 while (!m_events.empty()) { 20 dispatchEvent(); 21 } 22 23 disposeEvents(); 24 } 25 26 void GHOST_EventManager::dispatchEvent() 27 { 28 GHOST_IEvent *event = m_events.back(); 29 m_events.pop_back(); 30 m_handled_events.push_back(event); 31 32 dispatchEvent(event); 33 } 34 35 void GHOST_EventManager::dispatchEvent(GHOST_IEvent *event) 36 { 37 TConsumerVector::iterator iter; 38 39 for (iter = m_consumers.begin(); iter != m_consumers.end(); ++iter) { 40 (*iter)->processEvent(event); 41 } 42 }
11行,就是调用事件管理器m_eventManager来处理GHOST事件,35行的函数对事件进行处理,它是通过调用GHOST_CallbackEventConsumer类对象中的函数来实现,具体实现可参考GHOST_CallbackEventConsumer类代码。
至此,Blender中的event处理过程就结束了。
各种操作如果需要用户交互时,将使用事件处理器,
第13行,就是处理事件事件处理器中所有操作wm_event_do_handlers函数如下:
1 /* called in main loop */ 2 /* goes over entire hierarchy: events -> window -> screen -> area -> region */ 3 void wm_event_do_handlers(bContext *C) 4 { 5 wmWindowManager *wm = CTX_wm_manager(C); 6 wmWindow *win; 7 8 /* update key configuration before handling events */ 9 WM_keyconfig_update(wm); 10 WM_manipulatorconfig_update(CTX_data_main(C)); 11 12 for (win = wm->windows.first; win; win = win->next) { 13 bScreen *screen = WM_window_get_active_screen(win); 14 wmEvent *event; 15 16 /* some safty checks - these should always be set! */ 17 BLI_assert(WM_window_get_active_scene(win)); 18 BLI_assert(WM_window_get_active_screen(win)); 19 BLI_assert(WM_window_get_active_workspace(win)); 20 21 if (screen == NULL) 22 wm_event_free_all(win); 23 else { 24 Scene *scene = WM_window_get_active_scene(win); 25 26 if (scene) { 27 int is_playing_sound = BKE_sound_scene_playing(scene); 28 29 if (is_playing_sound != -1) { 30 bool is_playing_screen; 31 CTX_wm_window_set(C, win); 32 CTX_data_scene_set(C, scene); 33 34 is_playing_screen = (ED_screen_animation_playing(wm) != NULL); 35 36 if (((is_playing_sound == 1) && (is_playing_screen == 0)) || 37 ((is_playing_sound == 0) && (is_playing_screen == 1))) 38 { 39 ED_screen_animation_play(C, -1, 1); 40 } 41 42 if (is_playing_sound == 0) { 43 const float time = BKE_sound_sync_scene(scene); 44 if (isfinite(time)) { 45 int ncfra = time * (float)FPS + 0.5f; 46 if (ncfra != scene->r.cfra) { 47 scene->r.cfra = ncfra; 48 ViewLayer *view_layer = CTX_data_view_layer(C); 49 Depsgraph *depsgraph = CTX_data_depsgraph(C); 50 ED_update_for_newframe(CTX_data_main(C), scene, view_layer, depsgraph); 51 WM_event_add_notifier(C, NC_WINDOW, NULL); 52 } 53 } 54 } 55 56 CTX_data_scene_set(C, NULL); 57 CTX_wm_screen_set(C, NULL); 58 CTX_wm_window_set(C, NULL); 59 } 60 } 61 } 62 63 while ( (event = win->queue.first) ) { 64 int action = WM_HANDLER_CONTINUE; 65 66 /* active screen might change during handlers, update pointer */ 67 screen = WM_window_get_active_screen(win); 68 69 if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS) && !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { 70 printf("\n%s: Handling event\n", __func__); 71 WM_event_print(event); 72 } 73 74 /* take care of pie event filter */ 75 if (wm_event_pie_filter(win, event)) { 76 if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS) && !ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { 77 printf("\n%s: event filtered due to pie button pressed\n", __func__); 78 } 79 BLI_remlink(&win->queue, event); 80 wm_event_free(event); 81 continue; 82 } 83 84 CTX_wm_window_set(C, win); 85 86 /* we let modal handlers get active area/region, also wm_paintcursor_test needs it */ 87 CTX_wm_area_set(C, area_event_inside(C, &event->x)); 88 CTX_wm_region_set(C, region_event_inside(C, &event->x)); 89 90 /* MVC demands to not draw in event handlers... but we need to leave it for ogl selecting etc */ 91 wm_window_make_drawable(wm, win); 92 93 wm_region_mouse_co(C, event); 94 95 96 /* first we do priority handlers, modal + some limited keymaps */ 97 action |= wm_handlers_do(C, event, &win->modalhandlers); 98 99 /* fileread case */ 100 if (CTX_wm_window(C) == NULL) 101 return; 102 103 /* check dragging, creates new event or frees, adds draw tag */ 104 wm_event_drag_test(wm, win, event); 105 106 /* builtin tweak, if action is break it removes tweak */ 107 wm_tweakevent_test(C, event, action); 108 109 if ((action & WM_HANDLER_BREAK) == 0) { 110 ScrArea *sa; 111 ARegion *ar; 112 113 /* Note: setting subwin active should be done here, after modal handlers have been done */ 114 if (event->type == MOUSEMOVE) { 115 /* state variables in screen, cursors. Also used in wm_draw.c, fails for modal handlers though */ 116 ED_screen_set_subwinactive(C, event); 117 /* for regions having custom cursors */ 118 wm_paintcursor_test(C, event); 119 } 120 #ifdef WITH_INPUT_NDOF 121 else if (event->type == NDOF_MOTION) { 122 win->addmousemove = true; 123 } 124 #endif 125 126 for (sa = screen->areabase.first; sa; sa = sa->next) { 127 /* after restoring a screen from SCREENMAXIMIZED we have to wait 128 * with the screen handling till the region coordinates are updated */ 129 if (screen->skip_handling == true) { 130 /* restore for the next iteration of wm_event_do_handlers */ 131 screen->skip_handling = false; 132 break; 133 } 134 135 /* update azones if needed - done here because it needs to be independent from redraws */ 136 if (sa->flag & AREA_FLAG_ACTIONZONES_UPDATE) { 137 ED_area_azones_update(sa, &event->x); 138 } 139 140 if (wm_event_inside_i(event, &sa->totrct)) { 141 CTX_wm_area_set(C, sa); 142 143 if ((action & WM_HANDLER_BREAK) == 0) { 144 for (ar = sa->regionbase.first; ar; ar = ar->next) { 145 if (wm_event_inside_i(event, &ar->winrct)) { 146 CTX_wm_region_set(C, ar); 147 148 /* call even on non mouse events, since the */ 149 wm_region_mouse_co(C, event); 150 151 if (!BLI_listbase_is_empty(&wm->drags)) { 152 /* does polls for drop regions and checks uibuts */ 153 /* need to be here to make sure region context is true */ 154 if (ELEM(event->type, MOUSEMOVE, EVT_DROP) || ISKEYMODIFIER(event->type)) { 155 wm_drags_check_ops(C, event); 156 } 157 } 158 159 #ifdef USE_WORKSPACE_TOOL 160 /* How to solve properly? 161 * 162 * Handlers are stored in each region, 163 * however the tool-system swaps keymaps often and isn't stored 164 * per region. 165 * 166 * Need to investigate how this could be done better. 167 * We might need to add a more dynamic handler type that uses a callback 168 * to fetch its current keymap. 169 */ 170 wmEventHandler sneaky_handler = {NULL}; 171 if (ar->regiontype == RGN_TYPE_WINDOW) { 172 WorkSpace *workspace = WM_window_get_active_workspace(win); 173 if (workspace->tool.keymap[0] && 174 workspace->tool.spacetype == sa->spacetype) 175 { 176 wmKeyMap *km = WM_keymap_find_all( 177 C, workspace->tool.keymap, sa->spacetype, RGN_TYPE_WINDOW); 178 if (km != NULL) { 179 sneaky_handler.keymap = km; 180 /* Handle widgets first. */ 181 wmEventHandler *handler_last = ar->handlers.last; 182 while (handler_last && handler_last->manipulator_map == NULL) { 183 handler_last = handler_last->prev; 184 } 185 /* Head of list or after last manipulator. */ 186 BLI_insertlinkafter(&ar->handlers, handler_last, &sneaky_handler); 187 } 188 } 189 } 190 #endif /* USE_WORKSPACE_TOOL */ 191 192 action |= wm_handlers_do(C, event, &ar->handlers); 193 194 #ifdef USE_WORKSPACE_TOOL 195 if (sneaky_handler.keymap) { 196 BLI_remlink(&ar->handlers, &sneaky_handler); 197 } 198 #endif /* USE_WORKSPACE_TOOL */ 199 200 /* fileread case (python), [#29489] */ 201 if (CTX_wm_window(C) == NULL) 202 return; 203 204 if (action & WM_HANDLER_BREAK) 205 break; 206 } 207 } 208 } 209 210 CTX_wm_region_set(C, NULL); 211 212 if ((action & WM_HANDLER_BREAK) == 0) { 213 wm_region_mouse_co(C, event); /* only invalidates event->mval in this case */ 214 action |= wm_handlers_do(C, event, &sa->handlers); 215 } 216 CTX_wm_area_set(C, NULL); 217 218 /* NOTE: do not escape on WM_HANDLER_BREAK, mousemove needs handled for previous area */ 219 } 220 } 221 222 if ((action & WM_HANDLER_BREAK) == 0) { 223 /* also some non-modal handlers need active area/region */ 224 CTX_wm_area_set(C, area_event_inside(C, &event->x)); 225 CTX_wm_region_set(C, region_event_inside(C, &event->x)); 226 227 wm_region_mouse_co(C, event); 228 229 action |= wm_handlers_do(C, event, &win->handlers); 230 231 /* fileread case */ 232 if (CTX_wm_window(C) == NULL) 233 return; 234 } 235 236 } 237 238 /* update previous mouse position for following events to use */ 239 win->eventstate->prevx = event->x; 240 win->eventstate->prevy = event->y; 241 242 /* unlink and free here, blender-quit then frees all */ 243 BLI_remlink(&win->queue, event); 244 wm_event_free(event); 245 246 } 247 248 /* only add mousemove when queue was read entirely */ 249 if (win->addmousemove && win->eventstate) { 250 wmEvent tevent = *(win->eventstate); 251 // printf("adding MOUSEMOVE %d %d\n", tevent.x, tevent.y); 252 tevent.type = MOUSEMOVE; 253 tevent.prevx = tevent.x; 254 tevent.prevy = tevent.y; 255 wm_event_add(win, &tevent); 256 win->addmousemove = 0; 257 } 258 259 CTX_wm_window_set(C, NULL); 260 } 261 262 /* update key configuration after handling events */ 263 WM_keyconfig_update(wm); 264 WM_manipulatorconfig_update(CTX_data_main(C)); 265 }
接着调用wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)来处理
1 /* this calls handlers twice - to solve (double-)click events二次调用,解决鼠标二击事件 */ 2 static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers) 3 { 4 int action = wm_handlers_do_intern(C, event, handlers); 5 6 /* fileread case */ 7 if (CTX_wm_window(C) == NULL) 8 return action; 9 10 if (!ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE, EVENT_NONE) && !ISTIMER(event->type)) { 11 12 /* test for CLICK events */ 13 if (wm_action_not_handled(action)) { 14 wmWindow *win = CTX_wm_window(C); 15 16 /* eventstate stores if previous event was a KM_PRESS, in case that 17 * wasn't handled, the KM_RELEASE will become a KM_CLICK */ 18 19 if (win && event->val == KM_PRESS) { 20 win->eventstate->check_click = true; 21 } 22 23 if (win && win->eventstate->prevtype == event->type) { 24 25 if ((event->val == KM_RELEASE) && 26 (win->eventstate->prevval == KM_PRESS) && 27 (win->eventstate->check_click == true)) 28 { 29 if ((abs(event->x - win->eventstate->prevclickx)) <= WM_EVENT_CLICK_WIGGLE_ROOM && 30 (abs(event->y - win->eventstate->prevclicky)) <= WM_EVENT_CLICK_WIGGLE_ROOM) 31 { 32 event->val = KM_CLICK; 33 34 if (G.debug & (G_DEBUG_HANDLERS)) { 35 printf("%s: handling CLICK\n", __func__); 36 } 37 38 action |= wm_handlers_do_intern(C, event, handlers); 39 40 event->val = KM_RELEASE; 41 } 42 else { 43 win->eventstate->check_click = 0; 44 } 45 } 46 else if (event->val == KM_DBL_CLICK) { 47 event->val = KM_PRESS; 48 action |= wm_handlers_do_intern(C, event, handlers); 49 50 /* revert value if not handled */ 51 if (wm_action_not_handled(action)) { 52 event->val = KM_DBL_CLICK; 53 } 54 } 55 } 56 } 57 else { 58 wmWindow *win = CTX_wm_window(C); 59 60 if (win) 61 win->eventstate->check_click = 0; 62 } 63 } 64 65 return action; 66 }