786 lines
22 KiB
C++
786 lines
22 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2014 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <ShellScalingApi.h>
|
|
#include <string>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/platform_win.h"
|
|
#include "xenia/ui/window_win.h"
|
|
|
|
namespace xe {
|
|
namespace ui {
|
|
|
|
std::unique_ptr<Window> Window::Create(Loop* loop, const std::wstring& title) {
|
|
return std::make_unique<Win32Window>(loop, title);
|
|
}
|
|
|
|
Win32Window::Win32Window(Loop* loop, const std::wstring& title)
|
|
: Window(loop, title) {}
|
|
|
|
Win32Window::~Win32Window() {
|
|
OnDestroy();
|
|
if (hwnd_) {
|
|
SetWindowLongPtr(hwnd_, GWLP_USERDATA, 0);
|
|
CloseWindow(hwnd_);
|
|
hwnd_ = nullptr;
|
|
}
|
|
if (icon_) {
|
|
DestroyIcon(icon_);
|
|
icon_ = nullptr;
|
|
}
|
|
}
|
|
|
|
NativePlatformHandle Win32Window::native_platform_handle() const {
|
|
return ::GetModuleHandle(nullptr);
|
|
}
|
|
|
|
bool Win32Window::Initialize() { return OnCreate(); }
|
|
|
|
bool Win32Window::OnCreate() {
|
|
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
|
|
|
static bool has_registered_class = false;
|
|
if (!has_registered_class) {
|
|
// Tell Windows that we're DPI aware.
|
|
auto spda = (decltype(&SetProcessDpiAwareness))GetProcAddress(
|
|
GetModuleHandle(L"shcore.dll"), "SetProcessDpiAwareness");
|
|
if (spda) {
|
|
auto res = spda(PROCESS_PER_MONITOR_DPI_AWARE);
|
|
if (res != S_OK) {
|
|
XELOGE("Failed to set process DPI awareness. (code = 0x%.8X)", res);
|
|
}
|
|
}
|
|
|
|
WNDCLASSEX wcex;
|
|
wcex.cbSize = sizeof(WNDCLASSEX);
|
|
wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
|
wcex.lpfnWndProc = Win32Window::WndProcThunk;
|
|
wcex.cbClsExtra = 0;
|
|
wcex.cbWndExtra = 0;
|
|
wcex.hInstance = hInstance;
|
|
wcex.hIcon = LoadIcon(hInstance, L"MAINICON");
|
|
wcex.hIconSm = NULL; // LoadIcon(hInstance, L"MAINICON");
|
|
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
|
wcex.lpszMenuName = nullptr;
|
|
wcex.lpszClassName = L"XeniaWindowClass";
|
|
if (!RegisterClassEx(&wcex)) {
|
|
XELOGE("RegisterClassEx failed");
|
|
return false;
|
|
}
|
|
has_registered_class = true;
|
|
}
|
|
|
|
// Setup initial size.
|
|
DWORD window_style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
|
|
DWORD window_ex_style = WS_EX_APPWINDOW | WS_EX_CONTROLPARENT;
|
|
RECT rc = {0, 0, width_, height_};
|
|
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
|
|
|
|
// Create window.
|
|
hwnd_ = CreateWindowEx(window_ex_style, L"XeniaWindowClass", title_.c_str(),
|
|
window_style, rc.left, rc.top, rc.right - rc.left,
|
|
rc.bottom - rc.top, nullptr, nullptr, hInstance, this);
|
|
if (!hwnd_) {
|
|
XELOGE("CreateWindow failed");
|
|
return false;
|
|
}
|
|
|
|
// Disable flicks.
|
|
ATOM atom = GlobalAddAtom(L"MicrosoftTabletPenServiceProperty");
|
|
const DWORD_PTR dwHwndTabletProperty =
|
|
TABLET_DISABLE_PRESSANDHOLD | // disables press and hold (right-click)
|
|
// gesture
|
|
TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up (waves)
|
|
TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button
|
|
// down (circle)
|
|
TABLET_DISABLE_FLICKS | // disables pen flicks (back, forward, drag down,
|
|
// drag up)
|
|
TABLET_DISABLE_TOUCHSWITCH | TABLET_DISABLE_SMOOTHSCROLLING |
|
|
TABLET_DISABLE_TOUCHUIFORCEON | TABLET_ENABLE_MULTITOUCHDATA;
|
|
SetProp(hwnd_, L"MicrosoftTabletPenServiceProperty",
|
|
reinterpret_cast<HANDLE>(dwHwndTabletProperty));
|
|
GlobalDeleteAtom(atom);
|
|
|
|
// Enable DWM elevation.
|
|
EnableMMCSS();
|
|
// Enable file dragging from external sources
|
|
DragAcceptFiles(hwnd_, true);
|
|
|
|
ShowWindow(hwnd_, SW_SHOWNORMAL);
|
|
UpdateWindow(hwnd_);
|
|
|
|
arrow_cursor_ = LoadCursor(nullptr, IDC_ARROW);
|
|
|
|
// Initial state.
|
|
if (!is_cursor_visible_) {
|
|
ShowCursor(FALSE);
|
|
}
|
|
if (has_focus_) {
|
|
SetFocus(hwnd_);
|
|
}
|
|
|
|
return super::OnCreate();
|
|
}
|
|
|
|
void Win32Window::EnableMMCSS() {
|
|
HMODULE hLibrary = LoadLibrary(L"DWMAPI.DLL");
|
|
if (!hLibrary) {
|
|
return;
|
|
}
|
|
|
|
typedef HRESULT(__stdcall * PDwmEnableMMCSS)(BOOL);
|
|
PDwmEnableMMCSS pDwmEnableMMCSS =
|
|
(PDwmEnableMMCSS)GetProcAddress(hLibrary, "DwmEnableMMCSS");
|
|
if (pDwmEnableMMCSS) {
|
|
pDwmEnableMMCSS(TRUE);
|
|
}
|
|
|
|
typedef HRESULT(__stdcall * PDwmSetPresentParameters)(
|
|
HWND, DWM_PRESENT_PARAMETERS*);
|
|
PDwmSetPresentParameters pDwmSetPresentParameters =
|
|
(PDwmSetPresentParameters)GetProcAddress(hLibrary,
|
|
"DwmSetPresentParameters");
|
|
if (pDwmSetPresentParameters) {
|
|
DWM_PRESENT_PARAMETERS pp;
|
|
std::memset(&pp, 0, sizeof(DWM_PRESENT_PARAMETERS));
|
|
pp.cbSize = sizeof(DWM_PRESENT_PARAMETERS);
|
|
pp.fQueue = FALSE;
|
|
pp.cBuffer = 2;
|
|
pp.fUseSourceRate = FALSE;
|
|
pp.cRefreshesPerFrame = 1;
|
|
pp.eSampling = DWM_SOURCE_FRAME_SAMPLING_POINT;
|
|
pDwmSetPresentParameters(hwnd_, &pp);
|
|
}
|
|
|
|
FreeLibrary(hLibrary);
|
|
}
|
|
|
|
void Win32Window::OnDestroy() { super::OnDestroy(); }
|
|
|
|
void Win32Window::OnClose() {
|
|
if (!closing_ && hwnd_) {
|
|
closing_ = true;
|
|
}
|
|
super::OnClose();
|
|
}
|
|
|
|
void Win32Window::EnableMainMenu() {
|
|
if (main_menu_) {
|
|
main_menu_->EnableMenuItem(*this);
|
|
}
|
|
}
|
|
|
|
void Win32Window::DisableMainMenu() {
|
|
if (main_menu_) {
|
|
main_menu_->DisableMenuItem(*this);
|
|
}
|
|
}
|
|
|
|
bool Win32Window::set_title(const std::wstring& title) {
|
|
if (!super::set_title(title)) {
|
|
return false;
|
|
}
|
|
SetWindowText(hwnd_, title.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool Win32Window::SetIcon(const void* buffer, size_t size) {
|
|
if (icon_ != nullptr) {
|
|
DestroyIcon(icon_);
|
|
icon_ = nullptr;
|
|
}
|
|
|
|
// Reset icon to default.
|
|
auto default_icon = LoadIcon(GetModuleHandle(nullptr), L"MAINICON");
|
|
SendMessage(hwnd_, WM_SETICON, ICON_BIG,
|
|
reinterpret_cast<LPARAM>(default_icon));
|
|
SendMessage(hwnd_, WM_SETICON, ICON_SMALL,
|
|
reinterpret_cast<LPARAM>(default_icon));
|
|
if (!buffer || !size) {
|
|
return true;
|
|
}
|
|
|
|
// Create icon and set on window (if it's valid).
|
|
icon_ = CreateIconFromResourceEx(
|
|
reinterpret_cast<PBYTE>(const_cast<void*>(buffer)),
|
|
static_cast<DWORD>(size), TRUE, 0x00030000, 0, 0,
|
|
LR_DEFAULTCOLOR | LR_DEFAULTSIZE);
|
|
if (icon_) {
|
|
SendMessage(hwnd_, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(icon_));
|
|
SendMessage(hwnd_, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(icon_));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Win32Window::is_fullscreen() const { return fullscreen_; }
|
|
|
|
// https://blogs.msdn.microsoft.com/oldnewthing/20131017-00/?p=2903
|
|
BOOL UnadjustWindowRect(LPRECT prc, DWORD dwStyle, BOOL fMenu) {
|
|
RECT rc;
|
|
SetRectEmpty(&rc);
|
|
BOOL fRc = AdjustWindowRect(&rc, dwStyle, fMenu);
|
|
if (fRc) {
|
|
prc->left -= rc.left;
|
|
prc->top -= rc.top;
|
|
prc->right -= rc.right;
|
|
prc->bottom -= rc.bottom;
|
|
}
|
|
return fRc;
|
|
}
|
|
|
|
void Win32Window::ToggleFullscreen(bool fullscreen) {
|
|
if (fullscreen == is_fullscreen()) {
|
|
return;
|
|
}
|
|
|
|
DWORD style = GetWindowLong(hwnd_, GWL_STYLE);
|
|
if (fullscreen) {
|
|
// http://blogs.msdn.com/b/oldnewthing/archive/2010/04/12/9994016.aspx
|
|
MONITORINFO mi = {sizeof(mi)};
|
|
if (GetWindowPlacement(hwnd_, &windowed_pos_) &&
|
|
GetMonitorInfo(MonitorFromWindow(hwnd_, MONITOR_DEFAULTTOPRIMARY),
|
|
&mi)) {
|
|
// Remove the menubar and borders.
|
|
SetWindowLong(hwnd_, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW);
|
|
::SetMenu(hwnd_, NULL);
|
|
|
|
// Resize the window to fullscreen.
|
|
auto& rc = mi.rcMonitor;
|
|
AdjustWindowRect(&rc, GetWindowLong(hwnd_, GWL_STYLE), false);
|
|
MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
|
TRUE);
|
|
|
|
width_ = rc.right - rc.left;
|
|
height_ = rc.bottom - rc.top;
|
|
}
|
|
} else {
|
|
// Reinstate borders, resize to 1280x720
|
|
SetWindowLong(hwnd_, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
|
|
SetWindowPlacement(hwnd_, &windowed_pos_);
|
|
|
|
auto main_menu = reinterpret_cast<Win32MenuItem*>(main_menu_.get());
|
|
if (main_menu) {
|
|
::SetMenu(hwnd_, main_menu->handle());
|
|
}
|
|
|
|
auto& rc = windowed_pos_.rcNormalPosition;
|
|
bool has_menu = main_menu_ ? true : false;
|
|
UnadjustWindowRect(&rc, GetWindowLong(hwnd_, GWL_STYLE), has_menu);
|
|
width_ = rc.right - rc.left;
|
|
height_ = rc.bottom - rc.top;
|
|
}
|
|
|
|
fullscreen_ = fullscreen;
|
|
}
|
|
|
|
bool Win32Window::is_bordered() const {
|
|
DWORD style = GetWindowLong(hwnd_, GWL_STYLE);
|
|
return (style & WS_OVERLAPPEDWINDOW) == WS_OVERLAPPEDWINDOW;
|
|
}
|
|
|
|
void Win32Window::set_bordered(bool enabled) {
|
|
if (is_fullscreen()) {
|
|
// Don't screw with the borders if we're fullscreen.
|
|
return;
|
|
}
|
|
|
|
DWORD style = GetWindowLong(hwnd_, GWL_STYLE);
|
|
if (enabled) {
|
|
SetWindowLong(hwnd_, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
|
|
} else {
|
|
SetWindowLong(hwnd_, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW);
|
|
}
|
|
}
|
|
|
|
int Win32Window::get_dpi() const {
|
|
HMONITOR monitor = MonitorFromWindow(hwnd_, MONITOR_DEFAULTTOPRIMARY);
|
|
|
|
// According to msdn, x and y are identical...
|
|
UINT dpi_x, dpi_y;
|
|
GetDpiForMonitor(monitor, MDT_DEFAULT, &dpi_x, &dpi_y);
|
|
return dpi_x;
|
|
}
|
|
|
|
void Win32Window::set_cursor_visible(bool value) {
|
|
if (is_cursor_visible_ == value) {
|
|
return;
|
|
}
|
|
is_cursor_visible_ = value;
|
|
|
|
if (value) {
|
|
ShowCursor(TRUE);
|
|
} else {
|
|
ShowCursor(FALSE);
|
|
}
|
|
}
|
|
|
|
void Win32Window::set_focus(bool value) {
|
|
if (has_focus_ == value) {
|
|
return;
|
|
}
|
|
if (hwnd_) {
|
|
if (value) {
|
|
SetFocus(hwnd_);
|
|
} else {
|
|
SetFocus(nullptr);
|
|
}
|
|
} else {
|
|
has_focus_ = value;
|
|
}
|
|
}
|
|
|
|
void Win32Window::Resize(int32_t width, int32_t height) {
|
|
if (is_fullscreen()) {
|
|
// Cannot resize while in fullscreen.
|
|
return;
|
|
}
|
|
|
|
// Scale width and height
|
|
int32_t scaled_width, scaled_height;
|
|
float dpi_scale = get_dpi_scale();
|
|
scaled_width = int32_t(width * dpi_scale);
|
|
scaled_height = int32_t(height * dpi_scale);
|
|
|
|
RECT rc = {0, 0, 0, 0};
|
|
GetWindowRect(hwnd_, &rc);
|
|
if (rc.top < 0) {
|
|
rc.top = 100;
|
|
}
|
|
if (rc.left < 0) {
|
|
rc.left = 100;
|
|
}
|
|
|
|
rc.right = rc.left + scaled_width;
|
|
rc.bottom = rc.top + scaled_height;
|
|
|
|
bool has_menu = !is_fullscreen() && (main_menu_ ? true : false);
|
|
AdjustWindowRect(&rc, GetWindowLong(hwnd_, GWL_STYLE), has_menu);
|
|
MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
|
TRUE);
|
|
|
|
super::Resize(width, height);
|
|
}
|
|
|
|
void Win32Window::Resize(int32_t left, int32_t top, int32_t right,
|
|
int32_t bottom) {
|
|
if (is_fullscreen()) {
|
|
// Cannot resize while in fullscreen.
|
|
return;
|
|
}
|
|
|
|
RECT rc = {left, top, right, bottom};
|
|
|
|
// Scale width and height
|
|
float dpi_scale = get_dpi_scale();
|
|
rc.right = int32_t((right - left) * dpi_scale) + left;
|
|
rc.bottom = int32_t((bottom - top) * dpi_scale) + top;
|
|
|
|
bool has_menu = !is_fullscreen() && (main_menu_ ? true : false);
|
|
AdjustWindowRect(&rc, GetWindowLong(hwnd_, GWL_STYLE), has_menu);
|
|
MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
|
TRUE);
|
|
|
|
super::Resize(left, top, right, bottom);
|
|
}
|
|
|
|
void Win32Window::RawReposition(const RECT& rc) {
|
|
MoveWindow(hwnd_, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top,
|
|
TRUE);
|
|
}
|
|
|
|
void Win32Window::OnResize(UIEvent* e) {
|
|
RECT client_rect;
|
|
GetClientRect(hwnd_, &client_rect);
|
|
int32_t width = client_rect.right - client_rect.left;
|
|
int32_t height = client_rect.bottom - client_rect.top;
|
|
|
|
// Rescale to base DPI.
|
|
float dpi_scale = get_dpi_scale();
|
|
width = int32_t(width / dpi_scale);
|
|
height = int32_t(height / dpi_scale);
|
|
|
|
if (width != width_ || height != height_) {
|
|
width_ = width;
|
|
height_ = height;
|
|
Layout();
|
|
}
|
|
super::OnResize(e);
|
|
}
|
|
|
|
void Win32Window::Invalidate() {
|
|
super::Invalidate();
|
|
InvalidateRect(hwnd_, nullptr, FALSE);
|
|
}
|
|
|
|
void Win32Window::Close() {
|
|
if (closing_) {
|
|
return;
|
|
}
|
|
closing_ = true;
|
|
OnClose();
|
|
DestroyWindow(hwnd_);
|
|
}
|
|
|
|
void Win32Window::OnMainMenuChange() {
|
|
auto main_menu = reinterpret_cast<Win32MenuItem*>(main_menu_.get());
|
|
// Don't actually set the menu if we're fullscreen. We'll do that later.
|
|
if (main_menu && !is_fullscreen()) {
|
|
::SetMenu(hwnd_, main_menu->handle());
|
|
}
|
|
}
|
|
|
|
LRESULT CALLBACK Win32Window::WndProcThunk(HWND hWnd, UINT message,
|
|
WPARAM wParam, LPARAM lParam) {
|
|
Win32Window* window = nullptr;
|
|
if (message == WM_NCCREATE) {
|
|
auto create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam);
|
|
window = reinterpret_cast<Win32Window*>(create_struct->lpCreateParams);
|
|
SetWindowLongPtr(hWnd, GWLP_USERDATA, (__int3264)(LONG_PTR)window);
|
|
} else {
|
|
window =
|
|
reinterpret_cast<Win32Window*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
|
}
|
|
if (window) {
|
|
return window->WndProc(hWnd, message, wParam, lParam);
|
|
} else {
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
|
LPARAM lParam) {
|
|
if (hwnd_ != nullptr && hWnd != hwnd_) {
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) {
|
|
if (HandleMouse(message, wParam, lParam)) {
|
|
return 0;
|
|
} else {
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
} else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) {
|
|
if (HandleKeyboard(message, wParam, lParam)) {
|
|
return 0;
|
|
} else {
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
switch (message) {
|
|
case WM_DROPFILES: {
|
|
TCHAR lpszFile[MAX_PATH] = {0};
|
|
UINT uFiles = 0;
|
|
HDROP hDrop = (HDROP)wParam;
|
|
// Get number of files dropped
|
|
uFiles = DragQueryFile(hDrop, -1, NULL, NULL);
|
|
|
|
// Only getting first file dropped (other files ignored)
|
|
if (DragQueryFile(hDrop, 0, lpszFile, MAX_PATH)) {
|
|
auto e = FileDropEvent(this, lpszFile);
|
|
OnFileDrop(&e);
|
|
}
|
|
|
|
DragFinish(hDrop);
|
|
} break;
|
|
case WM_NCCREATE: {
|
|
// Tell Windows to automatically scale non-client areas on different DPIs.
|
|
auto en = (BOOL(WINAPI*)(HWND hwnd))GetProcAddress(
|
|
GetModuleHandle(L"user32.dll"), "EnableNonClientDpiScaling");
|
|
if (en) {
|
|
en(hWnd);
|
|
}
|
|
} break;
|
|
case WM_CREATE:
|
|
break;
|
|
case WM_DESTROY:
|
|
OnDestroy();
|
|
break;
|
|
case WM_CLOSE:
|
|
OnClose();
|
|
break;
|
|
|
|
case WM_MOVING:
|
|
break;
|
|
case WM_MOVE:
|
|
break;
|
|
case WM_SIZING:
|
|
break;
|
|
case WM_SIZE: {
|
|
auto e = UIEvent(this);
|
|
OnResize(&e);
|
|
} break;
|
|
|
|
case WM_PAINT: {
|
|
ValidateRect(hwnd_, nullptr);
|
|
static bool in_paint = false;
|
|
if (!in_paint) {
|
|
in_paint = true;
|
|
auto e = UIEvent(this);
|
|
OnPaint(&e);
|
|
in_paint = false;
|
|
}
|
|
return 0; // Ignored because of custom paint.
|
|
}
|
|
case WM_ERASEBKGND:
|
|
return 0; // Ignored because of custom paint.
|
|
case WM_DISPLAYCHANGE:
|
|
break;
|
|
case WM_DPICHANGED: {
|
|
LPRECT rect = (LPRECT)lParam;
|
|
if (rect) {
|
|
RawReposition(*rect);
|
|
}
|
|
|
|
auto e = UIEvent(this);
|
|
OnDpiChanged(&e);
|
|
} break;
|
|
|
|
case WM_ACTIVATEAPP:
|
|
case WM_SHOWWINDOW: {
|
|
if (wParam == TRUE) {
|
|
auto e = UIEvent(this);
|
|
OnVisible(&e);
|
|
} else {
|
|
auto e = UIEvent(this);
|
|
OnHidden(&e);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_KILLFOCUS: {
|
|
has_focus_ = false;
|
|
auto e = UIEvent(this);
|
|
OnLostFocus(&e);
|
|
break;
|
|
}
|
|
case WM_SETFOCUS: {
|
|
has_focus_ = true;
|
|
auto e = UIEvent(this);
|
|
OnGotFocus(&e);
|
|
break;
|
|
}
|
|
|
|
case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
|
|
return
|
|
// disables press and hold (right-click) gesture
|
|
TABLET_DISABLE_PRESSANDHOLD |
|
|
// disables UI feedback on pen up (waves)
|
|
TABLET_DISABLE_PENTAPFEEDBACK |
|
|
// disables UI feedback on pen button down (circle)
|
|
TABLET_DISABLE_PENBARRELFEEDBACK |
|
|
// disables pen flicks (back, forward, drag down, drag up)
|
|
TABLET_DISABLE_FLICKS | TABLET_DISABLE_TOUCHSWITCH |
|
|
TABLET_DISABLE_SMOOTHSCROLLING | TABLET_DISABLE_TOUCHUIFORCEON |
|
|
TABLET_ENABLE_MULTITOUCHDATA;
|
|
|
|
case WM_MENUCOMMAND: {
|
|
// TODO(benvanik): Redirect this to MenuItem's on_selected delegate.
|
|
MENUINFO menu_info = {0};
|
|
menu_info.cbSize = sizeof(menu_info);
|
|
menu_info.fMask = MIM_MENUDATA;
|
|
GetMenuInfo(HMENU(lParam), &menu_info);
|
|
auto parent_item = reinterpret_cast<Win32MenuItem*>(menu_info.dwMenuData);
|
|
auto child_item =
|
|
reinterpret_cast<Win32MenuItem*>(parent_item->child(wParam));
|
|
assert_not_null(child_item);
|
|
UIEvent e(this);
|
|
child_item->OnSelected(&e);
|
|
} break;
|
|
}
|
|
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
bool Win32Window::HandleMouse(UINT message, WPARAM wParam, LPARAM lParam) {
|
|
int32_t x = GET_X_LPARAM(lParam);
|
|
int32_t y = GET_Y_LPARAM(lParam);
|
|
if (message == WM_MOUSEWHEEL) {
|
|
POINT pt = {x, y};
|
|
ScreenToClient(hwnd_, &pt);
|
|
x = pt.x;
|
|
y = pt.y;
|
|
}
|
|
|
|
MouseEvent::Button button = MouseEvent::Button::kNone;
|
|
int32_t dx = x - last_mouse_pos_.x;
|
|
int32_t dy = y - last_mouse_pos_.y;
|
|
switch (message) {
|
|
case WM_LBUTTONDOWN:
|
|
case WM_LBUTTONUP:
|
|
button = MouseEvent::Button::kLeft;
|
|
break;
|
|
case WM_RBUTTONDOWN:
|
|
case WM_RBUTTONUP:
|
|
button = MouseEvent::Button::kRight;
|
|
break;
|
|
case WM_MBUTTONDOWN:
|
|
case WM_MBUTTONUP:
|
|
button = MouseEvent::Button::kMiddle;
|
|
break;
|
|
case WM_XBUTTONDOWN:
|
|
case WM_XBUTTONUP:
|
|
switch (GET_XBUTTON_WPARAM(wParam)) {
|
|
case XBUTTON1:
|
|
button = MouseEvent::Button::kX1;
|
|
break;
|
|
case XBUTTON2:
|
|
button = MouseEvent::Button::kX2;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
break;
|
|
case WM_MOUSEMOVE:
|
|
button = MouseEvent::Button::kNone;
|
|
break;
|
|
case WM_MOUSEWHEEL:
|
|
button = MouseEvent::Button::kNone;
|
|
dx = 0; // ?
|
|
dy = GET_WHEEL_DELTA_WPARAM(wParam);
|
|
break;
|
|
default:
|
|
// Double click/etc?
|
|
return true;
|
|
}
|
|
|
|
last_mouse_pos_ = {x, y};
|
|
|
|
auto e = MouseEvent(this, button, x, y, dx, dy);
|
|
switch (message) {
|
|
case WM_LBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_XBUTTONDOWN:
|
|
OnMouseDown(&e);
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
case WM_RBUTTONUP:
|
|
case WM_MBUTTONUP:
|
|
case WM_XBUTTONUP:
|
|
OnMouseUp(&e);
|
|
break;
|
|
case WM_MOUSEMOVE:
|
|
OnMouseMove(&e);
|
|
break;
|
|
case WM_MOUSEWHEEL:
|
|
OnMouseWheel(&e);
|
|
break;
|
|
}
|
|
return e.is_handled();
|
|
}
|
|
|
|
bool Win32Window::HandleKeyboard(UINT message, WPARAM wParam, LPARAM lParam) {
|
|
auto e = KeyEvent(
|
|
this, static_cast<int>(wParam), lParam & 0xFFFF0000, !!(lParam & 0x2),
|
|
!!(GetKeyState(VK_SHIFT) & 0x80), !!(GetKeyState(VK_CONTROL) & 0x80),
|
|
!!(GetKeyState(VK_MENU) & 0x80), !!(GetKeyState(VK_LWIN) & 0x80));
|
|
switch (message) {
|
|
case WM_KEYDOWN:
|
|
OnKeyDown(&e);
|
|
break;
|
|
case WM_KEYUP:
|
|
OnKeyUp(&e);
|
|
break;
|
|
case WM_CHAR:
|
|
OnKeyChar(&e);
|
|
break;
|
|
}
|
|
return e.is_handled();
|
|
}
|
|
|
|
std::unique_ptr<ui::MenuItem> MenuItem::Create(Type type,
|
|
const std::wstring& text,
|
|
const std::wstring& hotkey,
|
|
std::function<void()> callback) {
|
|
return std::make_unique<Win32MenuItem>(type, text, hotkey, callback);
|
|
}
|
|
|
|
Win32MenuItem::Win32MenuItem(Type type, const std::wstring& text,
|
|
const std::wstring& hotkey,
|
|
std::function<void()> callback)
|
|
: MenuItem(type, text, hotkey, std::move(callback)) {
|
|
switch (type) {
|
|
case MenuItem::Type::kNormal:
|
|
handle_ = CreateMenu();
|
|
break;
|
|
case MenuItem::Type::kPopup:
|
|
handle_ = CreatePopupMenu();
|
|
break;
|
|
default:
|
|
// May just be a placeholder.
|
|
break;
|
|
}
|
|
if (handle_) {
|
|
MENUINFO menu_info = {0};
|
|
menu_info.cbSize = sizeof(menu_info);
|
|
menu_info.fMask = MIM_MENUDATA | MIM_STYLE;
|
|
menu_info.dwMenuData = ULONG_PTR(this);
|
|
menu_info.dwStyle = MNS_NOTIFYBYPOS;
|
|
SetMenuInfo(handle_, &menu_info);
|
|
}
|
|
}
|
|
|
|
Win32MenuItem::~Win32MenuItem() {
|
|
if (handle_) {
|
|
DestroyMenu(handle_);
|
|
}
|
|
}
|
|
|
|
void Win32MenuItem::EnableMenuItem(Window& window) {
|
|
int i = 0;
|
|
for (auto iter = children_.begin(); iter != children_.end(); ++iter, i++) {
|
|
::EnableMenuItem(handle_, i, MF_BYPOSITION | MF_ENABLED);
|
|
}
|
|
DrawMenuBar((HWND)window.native_handle());
|
|
}
|
|
|
|
void Win32MenuItem::DisableMenuItem(Window& window) {
|
|
int i = 0;
|
|
for (auto iter = children_.begin(); iter != children_.end(); ++iter, i++) {
|
|
::EnableMenuItem(handle_, i, MF_BYPOSITION | MF_GRAYED);
|
|
}
|
|
DrawMenuBar((HWND)window.native_handle());
|
|
}
|
|
|
|
void Win32MenuItem::OnChildAdded(MenuItem* generic_child_item) {
|
|
auto child_item = static_cast<Win32MenuItem*>(generic_child_item);
|
|
|
|
switch (child_item->type()) {
|
|
case MenuItem::Type::kNormal:
|
|
// Nothing special.
|
|
break;
|
|
case MenuItem::Type::kPopup:
|
|
AppendMenuW(handle_, MF_POPUP,
|
|
reinterpret_cast<UINT_PTR>(child_item->handle()),
|
|
child_item->text().c_str());
|
|
break;
|
|
case MenuItem::Type::kSeparator:
|
|
AppendMenuW(handle_, MF_SEPARATOR, UINT_PTR(child_item->handle_), 0);
|
|
break;
|
|
case MenuItem::Type::kString:
|
|
auto full_name = child_item->text();
|
|
if (!child_item->hotkey().empty()) {
|
|
full_name += L"\t" + child_item->hotkey();
|
|
}
|
|
AppendMenuW(handle_, MF_STRING, UINT_PTR(child_item->handle_),
|
|
full_name.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Win32MenuItem::OnChildRemoved(MenuItem* generic_child_item) {}
|
|
|
|
} // namespace ui
|
|
} // namespace xe
|