// Copyright (C) 2010, Guy Barrand. All rights reserved. // See the file tools.license for terms. #ifndef toolx_Windows_glarea #define toolx_Windows_glarea #include #include #include #include #include #include #if defined(_MSC_VER) && _MSC_VER < 1900 #elif defined(__MINGW32__) #else #define TOOLX_WINDOWS_TOUCH #endif namespace toolx { namespace Windows { #ifdef TOOLX_WINDOWS_TOUCH inline bool is_message_touch_event() { // from https://stackoverflow.com/questions/29857587/detect-if-wm-mousemove-is-caused-by-touch-pen. static const LONG_PTR c_SIGNATURE_MASK = 0xFFFFFF00; static const LONG_PTR c_MOUSEEVENTF_FROMTOUCH = 0xFF515700; LONG_PTR extraInfo = ::GetMessageExtraInfo(); return ( ( extraInfo & c_SIGNATURE_MASK ) == c_MOUSEEVENTF_FROMTOUCH ); } #endif class glarea { static const std::string& s_class() { static const std::string s_v("toolx::Windows::glarea"); return s_v; } static void register_class(){ static bool s_done = false; //not const, then not thread safe. if(!s_done) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)proc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ::GetModuleHandle(NULL); wc.hIcon = LoadIcon(NULL,IDI_APPLICATION); wc.hCursor = LoadCursor(NULL,IDC_ARROW); wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); wc.lpszMenuName = (PTSTR)s_class().c_str(); wc.lpszClassName = (PTSTR)s_class().c_str(); ::RegisterClass(&wc); s_done = true; } } public: virtual void resize(unsigned int,unsigned int){} virtual void paint(unsigned int,unsigned int) {} virtual void close(){} virtual void left_button_up(unsigned int,unsigned int) {} virtual void left_button_down(unsigned int,unsigned int) {} virtual void mouse_move(unsigned int,unsigned int,bool) {} public: glarea(HWND a_parent) :m_parent(a_parent) ,m_hwnd(0) ,m_context(0) ,m_HDC(0) ,m_touch_available(false) ,m_interactor(0) { register_class(); RECT rect; ::GetClientRect(m_parent,&rect); m_hwnd = ::CreateWindow((PTSTR)s_class().c_str(), NULL, WS_CHILD | WS_VISIBLE, 0,0, rect.right-rect.left, rect.bottom-rect.top, m_parent,NULL, GetWindowInstance(m_parent), NULL); if(!m_hwnd) return; ::SetWindowLongPtr(m_hwnd,GWLP_USERDATA,LONG_PTR(this)); // initialize OpenGL rendering : m_HDC = ::GetDC(m_hwnd); if(m_HDC && SetWindowPixelFormat(m_HDC) ) { m_context = ::wglCreateContext(m_HDC); } #ifdef TOOLX_WINDOWS_TOUCH {BYTE digitizer_status = (BYTE)::GetSystemMetrics(SM_DIGITIZER); if((digitizer_status & (0x80 + 0x40)) == 0) { m_touch_available = false; } else { m_touch_available = true; if(!::RegisterTouchWindow(m_hwnd,0)) m_touch_available = false; }} #endif } virtual ~glarea(){ if(::wglGetCurrentContext()!=NULL) ::wglMakeCurrent(NULL,NULL); if(m_context) { ::wglDeleteContext(m_context); m_context = 0; } if(m_HDC && m_hwnd) ::ReleaseDC(m_hwnd,m_HDC); if(m_hwnd) { ::SetWindowLongPtr(m_hwnd,GWLP_USERDATA,LONG_PTR(NULL)); ::DestroyWindow(m_hwnd); m_hwnd = 0; } } protected: glarea(const glarea& a_from) :m_parent(a_from.m_parent) ,m_hwnd(0) ,m_context(0) ,m_HDC(0) ,m_touch_available(a_from.m_touch_available) ,m_interactor(0) { if(!m_parent) return; register_class(); RECT rect; ::GetClientRect(m_parent,&rect); m_hwnd = ::CreateWindow((PTSTR)s_class().c_str(), NULL, WS_CHILD | WS_VISIBLE, 0,0, rect.right-rect.left, rect.bottom-rect.top, m_parent,NULL, GetWindowInstance(m_parent), NULL); if(!m_hwnd) return; ::SetWindowLongPtr(m_hwnd,GWLP_USERDATA,LONG_PTR(this)); // initialize OpenGL rendering : m_HDC = ::GetDC(m_hwnd); if(m_HDC && SetWindowPixelFormat(m_HDC) ) { m_context = ::wglCreateContext(m_HDC); } #ifdef TOOLX_WINDOWS_TOUCH if(a_from.m_touch_available) { if(!::RegisterTouchWindow(m_hwnd,0)) m_touch_available = false; } #endif } protected: glarea& operator=(const glarea&){return *this;} public: void set_client_area_size(unsigned int a_w,unsigned int a_h) { RECT rect; ::GetClientRect(m_hwnd,&rect); ::MoveWindow(m_hwnd,rect.left,rect.top,a_w,a_h,TRUE); } HWND hwnd() const {return m_hwnd;} void post_WM_PAINT() const { ::PostMessage(m_hwnd,WM_PAINT,(WPARAM)0,(LPARAM)0); } void send_WM_PAINT() const { ::SendMessage(m_hwnd,WM_PAINT,(WPARAM)0,(LPARAM)0); } void wm_paint() { PAINTSTRUCT ps; BeginPaint(m_hwnd,&ps); if(m_HDC && m_context) { ::wglMakeCurrent(m_HDC,m_context); RECT rect; ::GetClientRect(m_hwnd,&rect); unsigned int w = rect.right-rect.left; unsigned int h = rect.bottom-rect.top; paint(w,h); ::SwapBuffers(m_HDC); ::wglMakeCurrent(m_HDC,0); } EndPaint(m_hwnd,&ps); } public: void set_device_interactor(tools::sg::device_interactor* a_interactor) {m_interactor = a_interactor;} //we do not have ownership. protected: bool is_touch_event() { #ifdef TOOLX_WINDOWS_TOUCH if(!m_touch_available) return false; return is_message_touch_event(); #else return false; #endif } protected: static LRESULT CALLBACK proc(HWND a_hwnd,UINT a_msg,WPARAM a_wparam,LPARAM a_lparam) { switch (a_msg) { case WM_SIZE:{ int width = LOWORD(a_lparam); int height = HIWORD(a_lparam); glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this) { _this->resize(width,height); } else { // CreateWindow send a WM_SIZE but GWLP_USERDATA not yet set. } }return 0; case WM_PAINT:{ glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this) _this->wm_paint(); }return 0; case WM_KEYDOWN:{ glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this) { if(_this->m_interactor) { tools::sg::key_down_event event(_this->convert(a_wparam)); _this->m_interactor->key_press(event); } } } return 0; case WM_KEYUP:{ glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this) { if(_this->m_interactor) { tools::sg::key_up_event event(_this->convert(a_wparam)); _this->m_interactor->key_release(event); } } } return 0; case WM_LBUTTONDOWN:{ glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this) { if(_this->m_interactor) { bool shift_modifier = ::GetKeyState(VK_SHIFT) & 0x8000; bool control_modifier = ::GetKeyState(VK_CONTROL) & 0x8000; tools::sg::mouse_down_event event(LOWORD(a_lparam),HIWORD(a_lparam),shift_modifier,control_modifier); _this->m_interactor->mouse_press(event); } else { RECT rect; ::GetClientRect(a_hwnd,&rect); unsigned int h = rect.bottom-rect.top; if(!_this->is_touch_event()) _this->left_button_down(LOWORD(a_lparam),h-HIWORD(a_lparam)); } } }return 0; case WM_LBUTTONUP:{ glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this) { if(_this->m_interactor) { bool shift_modifier = ::GetKeyState(VK_SHIFT) & 0x8000; bool control_modifier = ::GetKeyState(VK_CONTROL) & 0x8000; tools::sg::mouse_up_event event(LOWORD(a_lparam),HIWORD(a_lparam),shift_modifier,control_modifier); _this->m_interactor->mouse_release(event); } else { RECT rect; ::GetClientRect(a_hwnd,&rect); unsigned int h = rect.bottom-rect.top; if(!_this->is_touch_event()) _this->left_button_up(LOWORD(a_lparam),h-HIWORD(a_lparam)); } } } return 0; case WM_MOUSEMOVE:{ glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this) { WPARAM state = a_wparam; bool ldown = ((state & MK_LBUTTON)==MK_LBUTTON)?true:false; if(_this->m_interactor) { if(ldown) { bool shift_modifier = ::GetKeyState(VK_SHIFT) & 0x8000; bool control_modifier = ::GetKeyState(VK_CONTROL) & 0x8000; tools::sg::mouse_move_event event(LOWORD(a_lparam),HIWORD(a_lparam),shift_modifier,control_modifier,0,0,false); _this->m_interactor->mouse_move(event); } } else { RECT rect; ::GetClientRect(a_hwnd,&rect); unsigned int h = rect.bottom-rect.top; if(!_this->is_touch_event()) _this->mouse_move(LOWORD(a_lparam),h-HIWORD(a_lparam),ldown); } } }return 0; case WM_MOUSEWHEEL:{ glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this) { if(_this->m_interactor) { bool shift_modifier = ::GetKeyState(VK_SHIFT) & 0x8000; bool control_modifier = ::GetKeyState(VK_CONTROL) & 0x8000; POINT p; p.x = LOWORD(a_lparam); p.y = HIWORD(a_lparam); if(!::ScreenToClient(a_hwnd,&p)) {} tools::sg::wheel_rotate_event event(GET_WHEEL_DELTA_WPARAM(a_wparam),int(p.x),int(p.y),shift_modifier,control_modifier); _this->m_interactor->wheel_rotate(event); } } } return 0; #ifdef TOOLX_WINDOWS_TOUCH case WM_TOUCH:{ glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this && _this->m_touch_available) { RECT rect; ::GetClientRect(a_hwnd,&rect); unsigned int h = rect.bottom-rect.top; unsigned int num_inputs = (int)a_wparam; TOUCHINPUT* ti = new TOUCHINPUT[num_inputs]; POINT p; if(::GetTouchInputInfo((HTOUCHINPUT)a_lparam,num_inputs,ti,sizeof(TOUCHINPUT))) { for(unsigned int i=0;ileft_button_down(p.x,h-p.y); } else if (ti[i].dwFlags & TOUCHEVENTF_UP) { _this->left_button_up(p.x,h-p.y); } else if (ti[i].dwFlags & TOUCHEVENTF_MOVE) { bool ldown = true; //we assume that a TOUCHEVENT_DOWN had been done. _this->mouse_move(p.x,h-p.y,ldown); } } } ::CloseTouchInputHandle((HTOUCHINPUT)a_lparam); delete [] ti; } }return 0; #endif //TOOLX_WINDOWS_TOUCH case WM_DESTROY:wm__destroy(a_hwnd);return 0; } return (DefWindowProc(a_hwnd,a_msg,a_wparam,a_lparam)); } static bool SetWindowPixelFormat(HDC a_HDC){ PIXELFORMATDESCRIPTOR pfd; pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_STEREO_DONTCARE; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 32; pfd.cRedBits = 8; pfd.cRedShift = 16; pfd.cGreenBits = 8; pfd.cGreenShift = 8; pfd.cBlueBits = 8; pfd.cBlueShift = 0; pfd.cAlphaBits = 0; pfd.cAlphaShift = 0; pfd.cAccumBits = 64; pfd.cAccumRedBits = 16; pfd.cAccumGreenBits = 16; pfd.cAccumBlueBits = 16; pfd.cAccumAlphaBits = 0; pfd.cDepthBits = 32; pfd.cStencilBits = 8; pfd.cAuxBuffers = 0; pfd.iLayerType = PFD_MAIN_PLANE; pfd.bReserved = 0; pfd.dwLayerMask = 0; pfd.dwVisibleMask = 0; pfd.dwDamageMask = 0; int pixelIndex = ::ChoosePixelFormat(a_HDC,&pfd); if (pixelIndex==0) { // Let's choose a default index. pixelIndex = 1; if (::DescribePixelFormat(a_HDC, pixelIndex, sizeof(PIXELFORMATDESCRIPTOR), &pfd)==0) { return false; } } if (::SetPixelFormat(a_HDC,pixelIndex,&pfd)==FALSE) return false; return true; } static void wm__destroy(HWND a_hwnd) { glarea* _this = (glarea*)::GetWindowLongPtr(a_hwnd,GWLP_USERDATA); if(_this) { //How to be sure that we have a glarea* ??? _this->close(); if(_this->m_hwnd!=a_hwnd) { } _this->m_hwnd = 0; } ::SetWindowLongPtr(a_hwnd,GWLP_USERDATA,LONG_PTR(NULL)); } protected: tools::key_code convert(WPARAM a_key) { if(a_key==VK_SHIFT) return tools::sg::key_shift(); return (tools::key_code)a_key; } protected: HWND m_parent; HWND m_hwnd; HGLRC m_context; HDC m_HDC; bool m_touch_available; tools::sg::device_interactor* m_interactor; }; }} #endif