/* * Copyright 2010-2016 OpenXcom Developers. * * This file is part of OpenXcom. * * OpenXcom 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. * * OpenXcom 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 OpenXcom. If not, see . */ #include "InteractiveSurface.h" #include "Action.h" namespace OpenXcom { const SDLKey InteractiveSurface::SDLK_ANY = (SDLKey)-1; // using an unused keycode to represent an "any key" /** * Sets up a blank interactive surface with the specified size and position. * @param width Width in pixels. * @param height Height in pixels. * @param x X position in pixels. * @param y Y position in pixels. */ InteractiveSurface::InteractiveSurface(int width, int height, int x, int y) : Surface(width, height, x, y), _buttonsPressed(0), _in(0), _over(0), _out(0), _isHovered(false), _isFocused(true), _listButton(false) { } /** * */ InteractiveSurface::~InteractiveSurface() { } bool InteractiveSurface::isButtonHandled(Uint8 button) { bool handled = (_click.find(0) != _click.end() || _press.find(0) != _press.end() || _release.find(0) != _release.end()); if (!handled && button != 0) { handled = (_click.find(button) != _click.end() || _press.find(button) != _press.end() || _release.find(button) != _release.end()); } return handled; } bool InteractiveSurface::isButtonPressed(Uint8 button) const { if (button == 0) { return (_buttonsPressed != 0); } else { return (_buttonsPressed & SDL_BUTTON(button)) != 0; } } void InteractiveSurface::setButtonPressed(Uint8 button, bool pressed) { if (pressed) { _buttonsPressed |= SDL_BUTTON(button); } else { _buttonsPressed &= (~SDL_BUTTON(button)); } } /** * Changes the visibility of the surface. A hidden surface * isn't blitted nor receives events. * @param visible New visibility. */ void InteractiveSurface::setVisible(bool visible) { Surface::setVisible(visible); // Unpress button if it was hidden if (!_visible) { unpress(0); } } /** * Called whenever an action occurs, and processes it to * check if it's relevant to the surface and convert it * into a meaningful interaction like a "click", calling * the respective handlers. * @param action Pointer to an action. * @param state State that the action handlers belong to. */ void InteractiveSurface::handle(Action *action, State *state) { if (!_visible || _hidden) return; action->setSender(this); if (action->getDetails()->type == SDL_MOUSEBUTTONUP || action->getDetails()->type == SDL_MOUSEBUTTONDOWN) { action->setMouseAction(action->getDetails()->button.x, action->getDetails()->button.y, getX(), getY()); } else if (action->getDetails()->type == SDL_MOUSEMOTION) { action->setMouseAction(action->getDetails()->motion.x, action->getDetails()->motion.y, getX(), getY()); } if (action->isMouseAction()) { if ((action->getAbsoluteXMouse() >= getX() && action->getAbsoluteXMouse() < getX() + getWidth()) && (action->getAbsoluteYMouse() >= getY() && action->getAbsoluteYMouse() < getY() + getHeight())) { if (!_isHovered) { _isHovered = true; mouseIn(action, state); } if (_listButton && action->getDetails()->type == SDL_MOUSEMOTION) { _buttonsPressed = SDL_GetMouseState(0, 0); for (Uint8 i = 1; i <= NUM_BUTTONS; ++i) { if (isButtonPressed(i)) { action->getDetails()->button.button = i; mousePress(action, state); } } } mouseOver(action, state); } else { if (_isHovered) { _isHovered = false; mouseOut(action, state); if (_listButton && action->getDetails()->type == SDL_MOUSEMOTION) { for (Uint8 i = 1; i <= NUM_BUTTONS; ++i) { if (isButtonPressed(i)) { setButtonPressed(i, false); } action->getDetails()->button.button = i; mouseRelease(action, state); } } } } } if (action->getDetails()->type == SDL_MOUSEBUTTONDOWN) { if (_isHovered && !isButtonPressed(action->getDetails()->button.button)) { setButtonPressed(action->getDetails()->button.button, true); mousePress(action, state); } } else if (action->getDetails()->type == SDL_MOUSEBUTTONUP) { if (isButtonPressed(action->getDetails()->button.button)) { setButtonPressed(action->getDetails()->button.button, false); mouseRelease(action, state); if (_isHovered) { mouseClick(action, state); } } } if (_isFocused) { if (action->getDetails()->type == SDL_KEYDOWN) { keyboardPress(action, state); } else if (action->getDetails()->type == SDL_KEYUP) { keyboardRelease(action, state); } } } /** * Changes the surface's focus. Surfaces will only receive * keyboard events if focused. * @param focus Is it focused? */ void InteractiveSurface::setFocus(bool focus) { _isFocused = focus; } /** * Returns the surface's focus. Surfaces will only receive * keyboard events if focused. * @return Is it focused? */ bool InteractiveSurface::isFocused() const { return _isFocused; } /** * Simulates a "mouse button release". Used in circumstances * where the surface is unpressed without user input. * @param state Pointer to running state. */ void InteractiveSurface::unpress(State *state) { if (isButtonPressed()) { _buttonsPressed = 0; SDL_Event ev; ev.type = SDL_MOUSEBUTTONUP; ev.button.button = SDL_BUTTON_LEFT; Action a = Action(&ev, 0.0, 0.0, 0, 0); mouseRelease(&a, state); } } /** * Called every time there's a mouse press over the surface. * Allows the surface to have custom functionality for this action, * and can be called externally to simulate the action. * @param action Pointer to an action. * @param state State that the action handlers belong to. */ void InteractiveSurface::mousePress(Action *action, State *state) { std::map::iterator allHandler = _press.find(0); std::map::iterator oneHandler = _press.find(action->getDetails()->button.button); if (allHandler != _press.end()) { ActionHandler handler = allHandler->second; (state->*handler)(action); } if (oneHandler != _press.end()) { ActionHandler handler = oneHandler->second; (state->*handler)(action); } } /** * Called every time there's a mouse release over the surface. * Allows the surface to have custom functionality for this action, * and can be called externally to simulate the action. * @param action Pointer to an action. * @param state State that the action handlers belong to. */ void InteractiveSurface::mouseRelease(Action *action, State *state) { std::map::iterator allHandler = _release.find(0); std::map::iterator oneHandler = _release.find(action->getDetails()->button.button); if (allHandler != _release.end()) { ActionHandler handler = allHandler->second; (state->*handler)(action); } if (oneHandler != _release.end()) { ActionHandler handler = oneHandler->second; (state->*handler)(action); } } /** * Called every time there's a mouse click on the surface. * Allows the surface to have custom functionality for this action, * and can be called externally to simulate the action. * @param action Pointer to an action. * @param state State that the action handlers belong to. */ void InteractiveSurface::mouseClick(Action *action, State *state) { std::map::iterator allHandler = _click.find(0); std::map::iterator oneHandler = _click.find(action->getDetails()->button.button); if (allHandler != _click.end()) { ActionHandler handler = allHandler->second; (state->*handler)(action); } if (oneHandler != _click.end()) { ActionHandler handler = oneHandler->second; (state->*handler)(action); } } /** * Called every time the mouse moves into the surface. * Allows the surface to have custom functionality for this action, * and can be called externally to simulate the action. * @param action Pointer to an action. * @param state State that the action handlers belong to. */ void InteractiveSurface::mouseIn(Action *action, State *state) { if (_in != 0) { (state->*_in)(action); } } /** * Called every time the mouse moves over the surface. * Allows the surface to have custom functionality for this action, * and can be called externally to simulate the action. * @param action Pointer to an action. * @param state State that the action handlers belong to. */ void InteractiveSurface::mouseOver(Action *action, State *state) { if (_over != 0) { (state->*_over)(action); } } /** * Called every time the mouse moves out of the surface. * Allows the surface to have custom functionality for this action, * and can be called externally to simulate the action. * @param action Pointer to an action. * @param state State that the action handlers belong to. */ void InteractiveSurface::mouseOut(Action *action, State *state) { if (_out != 0) { (state->*_out)(action); } } /** * Called every time there's a keyboard press when the surface is focused. * Allows the surface to have custom functionality for this action, * and can be called externally to simulate the action. * @param action Pointer to an action. * @param state State that the action handlers belong to. */ void InteractiveSurface::keyboardPress(Action *action, State *state) { std::map::iterator allHandler = _keyPress.find(SDLK_ANY); std::map::iterator oneHandler = _keyPress.find(action->getDetails()->key.keysym.sym); if (allHandler != _keyPress.end()) { ActionHandler handler = allHandler->second; (state->*handler)(action); } // Check if Ctrl, Alt and Shift aren't pressed bool mod = ((action->getDetails()->key.keysym.mod & (KMOD_CTRL|KMOD_ALT|KMOD_SHIFT)) != 0); if (oneHandler != _keyPress.end() && !mod) { ActionHandler handler = oneHandler->second; (state->*handler)(action); } } /** * Called every time there's a keyboard release over the surface. * Allows the surface to have custom functionality for this action, * and can be called externally to simulate the action. * @param action Pointer to an action. * @param state State that the action handlers belong to. */ void InteractiveSurface::keyboardRelease(Action *action, State *state) { std::map::iterator allHandler = _keyRelease.find(SDLK_ANY); std::map::iterator oneHandler = _keyRelease.find(action->getDetails()->key.keysym.sym); if (allHandler != _keyRelease.end()) { ActionHandler handler = allHandler->second; (state->*handler)(action); } // Check if Ctrl, Alt and Shift aren't pressed bool mod = ((action->getDetails()->key.keysym.mod & (KMOD_CTRL|KMOD_ALT|KMOD_SHIFT)) != 0); if (oneHandler != _keyRelease.end() && !mod) { ActionHandler handler = oneHandler->second; (state->*handler)(action); } } /** * Sets a function to be called every time the surface is mouse clicked. * @param handler Action handler. * @param button Mouse button to check for. Set to 0 for any button. */ void InteractiveSurface::onMouseClick(ActionHandler handler, Uint8 button) { if (handler != 0) { _click[button] = handler; } else { _click.erase(button); } } /** * Sets a function to be called every time the surface is mouse pressed. * @param handler Action handler. * @param button Mouse button to check for. Set to 0 for any button. */ void InteractiveSurface::onMousePress(ActionHandler handler, Uint8 button) { if (handler != 0) { _press[button] = handler; } else { _press.erase(button); } } /** * Sets a function to be called every time the surface is mouse released. * @param handler Action handler. * @param button Mouse button to check for. Set to 0 for any button. */ void InteractiveSurface::onMouseRelease(ActionHandler handler, Uint8 button) { if (handler != 0) { _release[button] = handler; } else { _release.erase(button); } } /** * Sets a function to be called every time the mouse moves into the surface. * @param handler Action handler. */ void InteractiveSurface::onMouseIn(ActionHandler handler) { _in = handler; } /** * Sets a function to be called every time the mouse moves over the surface. * @param handler Action handler. */ void InteractiveSurface::onMouseOver(ActionHandler handler) { _over = handler; } /** * Sets a function to be called every time the mouse moves out of the surface. * @param handler Action handler. */ void InteractiveSurface::onMouseOut(ActionHandler handler) { _out = handler; } /** * Sets a function to be called every time a key is pressed when the surface is focused. * @param handler Action handler. * @param key Keyboard button to check for (note: ignores key modifiers). Set to SDLK_ANY for any key. */ void InteractiveSurface::onKeyboardPress(ActionHandler handler, SDLKey key) { if (key == SDLK_UNKNOWN) { // Ignore unknown keys return; } if (handler != 0) { _keyPress[key] = handler; } else { _keyPress.erase(key); } } /** * Sets a function to be called every time a key is released when the surface is focused. * @param handler Action handler. * @param key Keyboard button to check for (note: ignores key modifiers). Set to SDLK_ANY for any key. */ void InteractiveSurface::onKeyboardRelease(ActionHandler handler, SDLKey key) { if (key == SDLK_UNKNOWN) { // Ignore unknown keys return; } if (handler != 0) { _keyRelease[key] = handler; } else { _keyRelease.erase(key); } } /** * Sets a flag for this button to say "i'm a member of a textList" to true. */ void InteractiveSurface::setListButton() { _listButton = true; } }