552 lines
14 KiB
C++
552 lines
14 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/system.h"
|
|
#include "engines/engine.h"
|
|
#include "bagel/mfc/afxwin.h"
|
|
#include "bagel/mfc/libs/event_loop.h"
|
|
#include "bagel/mfc/winnt.h"
|
|
|
|
namespace Bagel {
|
|
namespace MFC {
|
|
namespace Libs {
|
|
|
|
#define FRAME_RATE 50
|
|
|
|
void EventLoop::runEventLoop(bool isModalDialog) {
|
|
MSG msg;
|
|
|
|
while (!shouldQuit() && !_activeWindows.empty()) {
|
|
CWnd *activeWin = GetActiveWindow();
|
|
if (activeWin->_modalResult != DEFAULT_MODAL_RESULT)
|
|
break;
|
|
|
|
if (!GetMessage(msg))
|
|
break;
|
|
|
|
CWnd *mainWnd = GetActiveWindow();
|
|
if (msg.message != WM_NULL && mainWnd && !mainWnd->PreTranslateMessage(&msg) &&
|
|
(!isModalDialog || !mainWnd->IsDialogMessage(&msg))) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EventLoop::SetActiveWindow(CWnd *wnd) {
|
|
assert(!shouldQuit());
|
|
if (wnd == GetActiveWindow())
|
|
// Already the active window
|
|
return;
|
|
|
|
// If it's the first window added, and we don't have
|
|
// a main window defined, set it as the main window
|
|
if (_activeWindows.empty())
|
|
_mainWindow = wnd;
|
|
|
|
// Add the window to the list
|
|
// Note: Currently we don't supportly multiple
|
|
// open windows at the same time. Each new window
|
|
// is effectively a dialog on top of previous ones
|
|
|
|
if (!_activeWindows.empty()) {
|
|
auto *win = _activeWindows.top();
|
|
win->SendMessage(WM_ACTIVATE, MAKEWPARAM(WA_INACTIVE, 0), 0);
|
|
win->SendMessage(WM_PALETTECHANGED, (WPARAM)wnd);
|
|
}
|
|
|
|
_activeWindows.push(wnd);
|
|
wnd->SendMessage(WM_ACTIVATE, MAKEWPARAM(WA_ACTIVE, 0), 0);
|
|
wnd->SendMessage(WM_QUERYNEWPALETTE, 0, 0);
|
|
}
|
|
|
|
void EventLoop::PopActiveWindow() {
|
|
_activeWindows.top()->SendMessage(WM_ACTIVATE, MAKEWPARAM(WA_INACTIVE, 0), 0);
|
|
_activeWindows.pop();
|
|
|
|
if (!_activeWindows.empty()) {
|
|
CWnd *wnd = _activeWindows.top();
|
|
wnd->RedrawWindow(nullptr, nullptr, RDW_INVALIDATE | RDW_ALLCHILDREN);
|
|
wnd->SendMessage(WM_ACTIVATE, MAKEWPARAM(WA_ACTIVE, 0), 0);
|
|
}
|
|
}
|
|
|
|
void EventLoop::doModal(CWnd *wnd) {
|
|
SetActiveWindow(wnd);
|
|
runEventLoop(true);
|
|
if (GetActiveWindow() == wnd)
|
|
wnd->DestroyWindow();
|
|
}
|
|
|
|
void EventLoop::checkMessages() {
|
|
// Don't do any actual ScummVM event handling
|
|
// until at least one window has been set up
|
|
if (_activeWindows.empty())
|
|
return;
|
|
|
|
if (_messages.empty() && _idleCtr >= 0) {
|
|
if (!OnIdle(_idleCtr))
|
|
// OnIdle returning false means disabling permanently
|
|
_idleCtr = -1;
|
|
}
|
|
|
|
// Poll for event in ScummVM event manager
|
|
MSG priorMsg;
|
|
Libs::Event ev;
|
|
|
|
while (pollEvents(ev)) {
|
|
// Handle custom keybinding actions mapping back to keys
|
|
if (_keybindProc) {
|
|
if (ev.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
|
|
ev.type = Common::EVENT_KEYDOWN;
|
|
ev.kbd.keycode = _keybindProc(ev.customType);
|
|
} else if (ev.type == Common::EVENT_CUSTOM_ENGINE_ACTION_END) {
|
|
ev.type = Common::EVENT_KEYUP;
|
|
ev.kbd.keycode = _keybindProc(ev.customType);
|
|
}
|
|
}
|
|
|
|
HWND hWnd = nullptr;
|
|
setMessageWnd(ev, hWnd);
|
|
MSG msg = ev;
|
|
msg.hwnd = hWnd;
|
|
|
|
if (msg.message == WM_MOUSEMOVE &&
|
|
priorMsg.message == WM_MOUSEMOVE) {
|
|
// Preventing multiple sequential mouse move messages
|
|
priorMsg = msg;
|
|
} else if (msg.message != WM_NULL) {
|
|
if (priorMsg.message != WM_NULL)
|
|
_messages.push(priorMsg);
|
|
priorMsg = msg;
|
|
}
|
|
|
|
if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) {
|
|
// Update saved mouse position
|
|
_mousePos.x = LOWORD(msg.lParam);
|
|
_mousePos.y = HIWORD(msg.lParam);
|
|
|
|
// For mouse messages, if the highlighted control
|
|
// changes, generate a WM_SETCURSOR event
|
|
if (msg.hwnd != _highlightedWin) {
|
|
// Add mouse leave event if win is still alive
|
|
CWnd *highlightedWin = CWnd::FromHandle(_highlightedWin);
|
|
if (highlightedWin)
|
|
highlightedWin->PostMessage(WM_MOUSELEAVE);
|
|
|
|
// Switch to newly highlighted control
|
|
_highlightedWin = msg.hwnd;
|
|
if (_highlightedWin)
|
|
PostMessage(_highlightedWin,
|
|
WM_SETCURSOR, (WPARAM)msg.hwnd,
|
|
MAKELPARAM(HTCLIENT, msg.message)
|
|
);
|
|
}
|
|
} else if (msg.message == WM_QUIT) {
|
|
// Add a window message close message as well
|
|
MSG cmsg;
|
|
cmsg.message = WM_CLOSE;
|
|
cmsg.hwnd = hWnd;
|
|
_messages.push(cmsg);
|
|
}
|
|
}
|
|
|
|
if (priorMsg.message != WM_NULL)
|
|
_messages.push(priorMsg);
|
|
|
|
// If there are no pending messages,
|
|
// do a brief pause and check for frame updates
|
|
if (_messages.empty())
|
|
checkForFrameUpdate();
|
|
}
|
|
|
|
bool EventLoop::GetMessage(MSG &msg) {
|
|
checkMessages();
|
|
|
|
// Queue window/control repaints if needed
|
|
for (CWnd *wnd : _updateWnds) {
|
|
if (wnd->IsWindowDirty())
|
|
wnd->PostMessage(WM_PAINT);
|
|
}
|
|
|
|
_updateWnds.clear();
|
|
|
|
// Check for any existing messages
|
|
if (!_messages.empty()) {
|
|
msg = _messages.pop();
|
|
|
|
if (msg.hwnd) {
|
|
if ((msg.message == WM_KEYDOWN || msg.message == WM_KEYUP) &&
|
|
!_kbdHookProc.empty()) {
|
|
if (_kbdHookProc.front()(HC_ACTION, msg.wParam, msg.lParam))
|
|
msg.message = WM_NULL;
|
|
}
|
|
} else if (msg.message != WM_QUIT) {
|
|
msg.message = WM_NULL;
|
|
} else {
|
|
debug(1, "Got WM_QUIT message..");
|
|
}
|
|
} else {
|
|
msg.message = WM_NULL;
|
|
}
|
|
|
|
return !((msg.message == WM_QUIT) || (shouldQuit() && _messages.empty()));
|
|
}
|
|
|
|
void EventLoop::setMessageWnd(Common::Event &ev, HWND &hWnd) {
|
|
if (isMouseMsg(ev)) {
|
|
setMouseMessageWnd(ev, hWnd);
|
|
return;
|
|
}
|
|
|
|
if (_focusedWin && (ev.type == Common::EVENT_KEYDOWN ||
|
|
ev.type == Common::EVENT_KEYUP)) {
|
|
hWnd = _focusedWin->m_hWnd;
|
|
return;
|
|
}
|
|
|
|
if (isJoystickMsg(ev)) {
|
|
CWnd *joystickWin = CWnd::FromHandle(_joystickWin);
|
|
|
|
if (joystickWin) {
|
|
switch (ev.type) {
|
|
case Common::EVENT_JOYAXIS_MOTION:
|
|
if (ev.joystick.axis == 0)
|
|
_joystickPos.x = ev.joystick.position + JOYSTICK_REST_POS;
|
|
else
|
|
_joystickPos.y = ev.joystick.position + JOYSTICK_REST_POS;
|
|
|
|
joystickWin->SendMessage(MM_JOY1MOVE, JOYSTICKID1,
|
|
MAKELPARAM(_joystickPos.x, _joystickPos.y));
|
|
break;
|
|
|
|
default:
|
|
_joystickButtons = ev.joystick.button;
|
|
_joystickWin->SendMessage(MM_JOY1MOVE, JOYSTICKID1,
|
|
MAKELPARAM(_joystickPos.x, _joystickPos.y));
|
|
break;
|
|
}
|
|
}
|
|
|
|
hWnd = nullptr;
|
|
return;
|
|
}
|
|
|
|
// Fallback, send message to active window
|
|
CWnd *activeWin = _activeWindows.top();
|
|
hWnd = activeWin->m_hWnd;
|
|
}
|
|
|
|
void EventLoop::setMouseMessageWnd(Common::Event &ev, HWND &hWnd) {
|
|
// Handle mouse capture
|
|
if (_captureWin) {
|
|
hWnd = _captureWin->m_hWnd;
|
|
|
|
POINT pt;
|
|
pt.x = ev.mouse.x;
|
|
pt.y = ev.mouse.y;
|
|
mousePosToClient(_captureWin, pt);
|
|
|
|
ev.mouse.x = pt.x;
|
|
ev.mouse.y = pt.y;
|
|
return;
|
|
}
|
|
|
|
// Special case for mouse moves: if there's an modal dialog,
|
|
// mouse moves will still be routed to the main window
|
|
// if the mouse is outside the dialog bounds
|
|
CWnd *activeWin = _activeWindows.top();
|
|
if (ev.type == Common::EVENT_MOUSEMOVE &&
|
|
!activeWin->_windowRect.contains(ev.mouse)) {
|
|
hWnd = _mainWindow->m_hWnd;
|
|
return;
|
|
}
|
|
|
|
CWnd *wnd = _activeWindows.top();
|
|
hWnd = getMouseMessageWnd(ev, wnd);
|
|
}
|
|
|
|
HWND EventLoop::getMouseMessageWnd(Common::Event &ev, CWnd *parent) {
|
|
POINT pt;
|
|
pt.x = ev.mouse.x;
|
|
pt.y = ev.mouse.y;
|
|
|
|
if (!mousePosToClient(parent, pt))
|
|
return nullptr;
|
|
|
|
// Iterate through any children
|
|
for (const auto &node : parent->getChildren()) {
|
|
HWND child = getMouseMessageWnd(ev, node._value);
|
|
if (child)
|
|
return child;
|
|
}
|
|
|
|
// Final control under mouse
|
|
ev.mouse.x = pt.x;
|
|
ev.mouse.y = pt.y;
|
|
return parent;
|
|
}
|
|
|
|
bool EventLoop::mousePosToClient(CWnd *wnd, POINT &pt) {
|
|
RECT clientRect;
|
|
|
|
// Get the mouse position in passed window
|
|
wnd->ScreenToClient(&pt);
|
|
wnd->GetClientRect(&clientRect);
|
|
|
|
Common::Rect r = clientRect;
|
|
return r.contains(pt.x, pt.y);
|
|
}
|
|
|
|
bool EventLoop::pollEvents(Common::Event &event) {
|
|
return g_system->getEventManager()->pollEvent(event);
|
|
}
|
|
|
|
void EventLoop::checkForFrameUpdate() {
|
|
// Brief pauses and screen updates
|
|
g_system->delayMillis(10);
|
|
|
|
// Trigger any pending timers
|
|
triggerTimers();
|
|
|
|
// Handle screen updates
|
|
uint32 time = g_system->getMillis();
|
|
if (time >= _nextFrameTime) {
|
|
_nextFrameTime = time + (1000 / FRAME_RATE);
|
|
AfxGetApp()->getScreen()->update();
|
|
}
|
|
|
|
// Cleanup any temporary handle wrapper
|
|
AfxGetApp()->AfxUnlockTempMaps();
|
|
}
|
|
|
|
bool EventLoop::PeekMessage(LPMSG lpMsg, HWND hWnd,
|
|
unsigned int wMsgFilterMin, unsigned int wMsgFilterMax,
|
|
unsigned int wRemoveMsg) {
|
|
checkMessages();
|
|
return _messages.peekMessage(lpMsg, hWnd,
|
|
wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
|
|
}
|
|
|
|
bool EventLoop::PostMessage(HWND hWnd, unsigned int Msg,
|
|
WPARAM wParam, LPARAM lParam) {
|
|
if (shouldQuit())
|
|
return false;
|
|
if (!hWnd && Msg == WM_PARENTNOTIFY)
|
|
// Hodj minigame launched directly without metagame,
|
|
// so we can ignore the WM_PARENTNOTIFY on closure
|
|
return false;
|
|
|
|
assert(hWnd);
|
|
_messages.push(MSG(hWnd, Msg, wParam, lParam));
|
|
return true;
|
|
}
|
|
|
|
void EventLoop::TranslateMessage(LPMSG lpMsg) {
|
|
if ((lpMsg->message == WM_KEYDOWN || lpMsg->message == WM_SYSKEYDOWN) &&
|
|
(isChar((Common::KeyCode)lpMsg->wParam) || Common::isPrint(lpMsg->_ascii))) {
|
|
uint message = (lpMsg->message == WM_SYSKEYDOWN) ?
|
|
WM_SYSCHAR : WM_CHAR;
|
|
WPARAM wParam = lpMsg->_ascii;
|
|
LPARAM lParam = lpMsg->lParam;
|
|
PostMessage(lpMsg->hwnd, message, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
void EventLoop::DispatchMessage(LPMSG lpMsg) {
|
|
CWnd *wnd = CWnd::FromHandle(lpMsg->hwnd);
|
|
|
|
if (wnd) {
|
|
wnd->SendMessage(lpMsg->message,
|
|
lpMsg->wParam, lpMsg->lParam);
|
|
}
|
|
}
|
|
|
|
bool EventLoop::isMouseMsg(const Common::Event &ev) const {
|
|
return ev.type == Common::EVENT_MOUSEMOVE ||
|
|
ev.type == Common::EVENT_LBUTTONDOWN ||
|
|
ev.type == Common::EVENT_LBUTTONUP ||
|
|
ev.type == Common::EVENT_RBUTTONDOWN ||
|
|
ev.type == Common::EVENT_RBUTTONUP ||
|
|
ev.type == Common::EVENT_WHEELUP ||
|
|
ev.type == Common::EVENT_WHEELDOWN ||
|
|
ev.type == Common::EVENT_MBUTTONDOWN ||
|
|
ev.type == Common::EVENT_MBUTTONUP;;
|
|
}
|
|
|
|
bool EventLoop::isJoystickMsg(const Common::Event &ev) const {
|
|
return ev.type == Common::EVENT_JOYAXIS_MOTION ||
|
|
ev.type == Common::EVENT_JOYBUTTON_DOWN ||
|
|
ev.type == Common::EVENT_JOYBUTTON_UP;
|
|
}
|
|
|
|
bool EventLoop::shouldQuit() const {
|
|
return g_engine->shouldQuit();
|
|
}
|
|
|
|
void EventLoop::quit() {
|
|
g_engine->quitGame();
|
|
}
|
|
|
|
void EventLoop::SetCapture(HWND hWnd) {
|
|
_captureWin = hWnd;
|
|
}
|
|
|
|
void EventLoop::ReleaseCapture() {
|
|
_captureWin = nullptr;
|
|
}
|
|
|
|
HWND EventLoop::GetCapture() const {
|
|
return _captureWin;
|
|
}
|
|
|
|
void EventLoop::SetFocus(CWnd *wnd) {
|
|
HWND oldFocus = _focusedWin;
|
|
HWND newFocus = wnd ? wnd->m_hWnd : nullptr;
|
|
|
|
if (newFocus != _focusedWin) {
|
|
CWnd *focusedWin = CWnd::FromHandle(_focusedWin);
|
|
CWnd *newFocusedWin = CWnd::FromHandle(newFocus);
|
|
|
|
if (_focusChangeProc)
|
|
_focusChangeProc(focusedWin, newFocusedWin);
|
|
|
|
if (focusedWin) {
|
|
focusedWin->_hasFocus = false;
|
|
focusedWin->SendMessage(WM_KILLFOCUS,
|
|
wnd ? (WPARAM)newFocus : (WPARAM)nullptr);
|
|
}
|
|
|
|
_focusedWin = newFocus;
|
|
if (wnd) {
|
|
wnd->_hasFocus = true;
|
|
wnd->SendMessage(WM_SETFOCUS, (WPARAM)oldFocus);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EventLoop::setMousePos(const Common::Point &pt) {
|
|
_mousePos = pt;
|
|
g_system->warpMouse(pt.x, pt.y);
|
|
}
|
|
|
|
MMRESULT EventLoop::joySetCapture(HWND hwnd, unsigned int uJoyID,
|
|
unsigned int uPeriod, bool fChanged) {
|
|
assert(uJoyID == JOYSTICKID1);
|
|
_joystickWin = hwnd;
|
|
return JOYERR_NOERROR;
|
|
}
|
|
|
|
MMRESULT EventLoop::joySetThreshold(unsigned int uJoyID, unsigned int uThreshold) {
|
|
// No implementation
|
|
return JOYERR_NOERROR;
|
|
}
|
|
|
|
MMRESULT EventLoop::joyGetPos(unsigned int uJoyID, LPJOYINFO pji) {
|
|
assert(uJoyID == JOYSTICKID1);
|
|
|
|
pji->wXpos = _joystickPos.x;
|
|
pji->wYpos = _joystickPos.y;
|
|
pji->wZpos = 0;
|
|
pji->wButtons = _joystickButtons;
|
|
|
|
return JOYERR_NOERROR;
|
|
}
|
|
|
|
MMRESULT EventLoop::joyReleaseCapture(unsigned int uJoyID) {
|
|
assert(uJoyID == JOYSTICKID1);
|
|
return JOYERR_NOERROR;
|
|
}
|
|
|
|
uintptr EventLoop::SetTimer(HWND hWnd, uintptr nIDEvent, unsigned int nElapse,
|
|
void (CALLBACK *lpfnTimer)(HWND, unsigned int, uintptr, uint32)) {
|
|
if (!nIDEvent)
|
|
nIDEvent = ++_timerIdCtr;
|
|
|
|
_timers.push_back(TimerEntry(hWnd, nIDEvent, nElapse, lpfnTimer));
|
|
|
|
return nIDEvent;
|
|
}
|
|
|
|
bool EventLoop::KillTimer(HWND hWnd, uintptr nIDEvent) {
|
|
for (auto it = _timers.begin(); it != _timers.end(); ++it) {
|
|
if (it->_hWnd == hWnd && it->_idEvent == nIDEvent) {
|
|
_timers.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void EventLoop::triggerTimers() {
|
|
uint32 currTime = g_system->getMillis();
|
|
|
|
for (auto it = _timers.begin(); it != _timers.end(); ) {
|
|
if (currTime >= it->_nextTriggerTime) {
|
|
// First update the timer for the next time
|
|
it->_nextTriggerTime = currTime + it->_interval;
|
|
|
|
if (it->_callback) {
|
|
// Call the callback
|
|
it->_callback(it->_hWnd, WM_TIMER, it->_idEvent, currTime);
|
|
} else {
|
|
// Otherwise, send timer event
|
|
CWnd *wnd = CWnd::FromHandle(it->_hWnd);
|
|
if (wnd)
|
|
wnd->SendMessage(WM_TIMER, it->_idEvent, 0);
|
|
}
|
|
|
|
// Since it's conceivable that a timer callback might
|
|
// remove timers, always restart the iterator afterwards
|
|
it = _timers.begin();
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
EventLoop::TimerEntry::TimerEntry(HWND hWnd, uintptr idEvent,
|
|
uint32 interval, TimerProc callback) :
|
|
_hWnd(hWnd), _idEvent(idEvent),
|
|
_interval(interval), _callback(callback) {
|
|
_nextTriggerTime = g_system->getMillis() + interval;
|
|
}
|
|
|
|
void EventLoop::pause() {
|
|
// Pause and update screen
|
|
g_system->delayMillis(20);
|
|
AfxGetApp()->getScreen()->update();
|
|
AfxGetApp()->checkMessages();
|
|
}
|
|
|
|
bool EventLoop::isChar(Common::KeyCode kc) const {
|
|
return kc == Common::KEYCODE_SPACE ||
|
|
kc == Common::KEYCODE_TAB ||
|
|
kc == Common::KEYCODE_RETURN ||
|
|
kc == Common::KEYCODE_BACKSPACE ||
|
|
kc == Common::KEYCODE_ESCAPE;
|
|
}
|
|
|
|
} // namespace Libs
|
|
} // namespace MFC
|
|
} // namespace Bagel
|