Files
scummvm-cursorfix/engines/tetraedge/te/te_button_layout.cpp
2026-02-02 04:50:13 +01:00

414 lines
12 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 "tetraedge/tetraedge.h"
#include "tetraedge/te/te_button_layout.h"
#include "tetraedge/te/te_sound_manager.h"
#include "tetraedge/te/te_input_mgr.h"
#include "tetraedge/te/te_sprite_layout.h"
namespace Tetraedge {
class TeZPriorityMouseCallback : public TeCallback1Param<TeButtonLayout, const Common::Point &> {
public:
TeZPriorityMouseCallback(TeButtonLayout *layout, TMethod method) : TeCallback1Param<TeButtonLayout, const Common::Point &>(layout, method), _pri(0.0) {}
virtual float &priority() override {
_pri =_object->worldPosition().z();
return _pri;
}
float _pri;
};
/*static*/
bool TeButtonLayout::_mousePositionChangedCatched = false;
/*static*/
TeTimer *TeButtonLayout::_doubleValidationProtectionTimer = nullptr;
/*static*/
TeTimer *TeButtonLayout::getDoubleValidationProtectionTimer() {
if (!_doubleValidationProtectionTimer) {
_doubleValidationProtectionTimer = new TeTimer();
}
return _doubleValidationProtectionTimer;
}
TeButtonLayout::TeButtonLayout() : _currentState(BUTTON_STATE_UP),
_clickPassThrough(false), _validationSoundVolume(1.0),
_ignoreMouseEvents(false), _doubleValidationProtectionEnabled(true),
_upLayout(nullptr), _downLayout(nullptr), _rolloverLayout(nullptr),
_disabledLayout(nullptr), _hitZoneLayout(nullptr), _ownedLayouts(false)
{
_onMousePositionChangedMaxPriorityCallback.reset(new TeCallback1Param<TeButtonLayout, const Common::Point &>(this, &TeButtonLayout::onMousePositionChangedMaxPriority, FLT_MAX));
_onMousePositionChangedCallback.reset(new TeZPriorityMouseCallback(this, &TeButtonLayout::onMousePositionChanged));
_onMouseLeftDownCallback.reset(new TeZPriorityMouseCallback(this, &TeButtonLayout::onMouseLeftDown));
_onMouseLeftUpMaxPriorityCallback.reset(new TeCallback1Param<TeButtonLayout, const Common::Point &>(this, &TeButtonLayout::onMouseLeftUpMaxPriority, FLT_MAX));
_onMouseLeftUpCallback.reset(new TeZPriorityMouseCallback(this, &TeButtonLayout::onMouseLeftUp));
TeInputMgr *inputmgr = g_engine->getInputMgr();
inputmgr->_mouseMoveSignal.push_back(_onMousePositionChangedCallback);
inputmgr->_mouseMoveSignal.push_back(_onMousePositionChangedMaxPriorityCallback);
inputmgr->_mouseLDownSignal.push_back(_onMouseLeftDownCallback);
inputmgr->_mouseLUpSignal.push_back(_onMouseLeftUpCallback);
inputmgr->_mouseLUpSignal.push_back(_onMouseLeftUpMaxPriorityCallback);
setEditionColor(TeColor(128, 128, 128, 255));
if (!getDoubleValidationProtectionTimer()->running())
getDoubleValidationProtectionTimer()->start();
}
TeButtonLayout::~TeButtonLayout() {
TeInputMgr *inputmgr = g_engine->getInputMgr();
inputmgr->_mouseMoveSignal.remove(_onMousePositionChangedCallback);
inputmgr->_mouseMoveSignal.remove(_onMousePositionChangedMaxPriorityCallback);
inputmgr->_mouseLDownSignal.remove(_onMouseLeftDownCallback);
inputmgr->_mouseLUpSignal.remove(_onMouseLeftUpCallback);
inputmgr->_mouseLUpSignal.remove(_onMouseLeftUpMaxPriorityCallback);
if (_ownedLayouts) {
if (_upLayout)
delete _upLayout;
if (_downLayout)
delete _downLayout;
if (_rolloverLayout)
delete _rolloverLayout;
if (_hitZoneLayout)
delete _hitZoneLayout;
if (_disabledLayout)
delete _disabledLayout;
}
}
bool TeButtonLayout::isMouseIn(const TeVector2s32 &mouseloc) {
if (!_hitZoneLayout) {
return TeLayout::isMouseIn(mouseloc);
} else {
return _hitZoneLayout->isMouseIn(mouseloc);
}
}
void TeButtonLayout::load(const Common::Path &upImg, const Common::Path &downImg, const Common::Path &overImg) {
TeSpriteLayout *upSprite = nullptr;
if (!upImg.empty()) {
upSprite = new TeSpriteLayout();
if (!upSprite->load(upImg))
warning("Failed to load button up img %s", upImg.toString(Common::Path::kNativeSeparator).c_str());
}
setUpLayout(upSprite);
TeSpriteLayout *downSprite = nullptr;
if (!downImg.empty()) {
downSprite = new TeSpriteLayout();
if (!downSprite->load(downImg))
warning("Failed to load button down img %s", downImg.toString(Common::Path::kNativeSeparator).c_str());
}
setDownLayout(downSprite);
TeSpriteLayout *overSprite = nullptr;
if (!overImg.empty()) {
overSprite = new TeSpriteLayout();
if (!overSprite->load(overImg))
warning("Failed to load button over img %s", overImg.toString(Common::Path::kNativeSeparator).c_str());
}
setRollOverLayout(overSprite);
setHitZone(nullptr);
setDisabledLayout(nullptr);
_ownedLayouts = true;
}
bool TeButtonLayout::onMouseLeftDown(const Common::Point &pt) {
if (!worldVisible() || _currentState == BUTTON_STATE_DISABLED || _ignoreMouseEvents)
return false;
// Note: This doesn't exactly reproduce the original behavior, it's
// very simplified.
bool mouseIn = isMouseIn(pt);
//if (mouseIn)
// debug("mouse down on button '%s' (current state %d)", name().c_str(), _currentState);
enum State newState = _currentState;
switch (_currentState) {
case BUTTON_STATE_DOWN:
if (!mouseIn)
newState = BUTTON_STATE_UP;
/*
// TODO: should this be a click?
if (mouseIn) {
newState = BUTTON_STATE_UP;
debug("mouse clicked button '%s' (from leftdown)", name().c_str());
if (!_validationSound.empty()) {
TeSoundManager *sndMgr = g_engine->getSoundManager();
sndMgr->playFreeSound(_validationSound, _validationSoundVolume, "sfx");
}
setState(newState);
_onMouseClickValidatedSignal.call();
return !_clickPassThrough;
}*/
break;
case BUTTON_STATE_ROLLOVER:
case BUTTON_STATE_UP:
if (mouseIn)
newState = BUTTON_STATE_DOWN;
break;
case BUTTON_STATE_DISABLED:
break;
}
setState(newState);
return mouseIn && !_clickPassThrough;
}
bool TeButtonLayout::onMouseLeftUp(const Common::Point &pt) {
if (!worldVisible() || _currentState == BUTTON_STATE_DISABLED)
return false;
// Note: This doesn't exactly reproduce the original behavior, it's
// somewhat simplified.
bool mouseIn = isMouseIn(pt);
//if (mouseIn)
// debug("mouse up on button '%s' (current state %d)", name().c_str(), _currentState);
enum State newState = _currentState;
switch (_currentState) {
case BUTTON_STATE_DOWN:
newState = BUTTON_STATE_UP;
if (mouseIn) {
debug("mouse clicked button '%s' (from leftup)", name().c_str());
if (!_validationSound.empty()) {
TeSoundManager *sndMgr = g_engine->getSoundManager();
sndMgr->playFreeSound(_validationSound, _validationSoundVolume, "sfx");
}
setState(newState);
bool stopProcessing = _onMouseClickValidatedSignal.call();
return !_clickPassThrough || stopProcessing;
}
break;
case BUTTON_STATE_ROLLOVER:
case BUTTON_STATE_UP:
case BUTTON_STATE_DISABLED:
break;
}
setState(newState);
return mouseIn && !_clickPassThrough;
}
bool TeButtonLayout::onMousePositionChanged(const Common::Point &pt) {
if (!worldVisible() || _ignoreMouseEvents)
return false;
// Note: This doesn't exactly reproduce the original behavior, it's
// very simplified.
bool mouseIn = isMouseIn(pt);
enum State newState = _currentState;
switch (_currentState) {
case BUTTON_STATE_UP:
if (mouseIn) {
newState = BUTTON_STATE_ROLLOVER;
}
break;
case BUTTON_STATE_DOWN:
case BUTTON_STATE_ROLLOVER:
if (!mouseIn) {
newState = BUTTON_STATE_UP;
}
break;
case BUTTON_STATE_DISABLED:
break;
}
setState(newState);
return false;
}
void TeButtonLayout::reset() {
_intArr.clear();
State newState = (_currentState == BUTTON_STATE_DISABLED ? BUTTON_STATE_DISABLED : BUTTON_STATE_UP);
setState(newState);
}
void TeButtonLayout::resetTimeFromLastValidation() {
TeTimer *timer = getDoubleValidationProtectionTimer();
if (!timer->running()) {
timer->start();
}
timer->timeElapsed();
}
uint64 TeButtonLayout::timeFromLastValidation() {
// probably not needed because we reimplemented how this works.
error("TODO: Implement TeButtonLayout::timeFromLastValidation.");
}
void TeButtonLayout::setDisabledLayout(TeLayout *disabledLayout) {
if (_disabledLayout)
removeChild(_disabledLayout);
_disabledLayout = disabledLayout;
if (_disabledLayout) {
_sizeChanged = true;
addChild(_disabledLayout);
//_disabledLayout->setColor(TeColor(0, 0, 0, 0));
//_disabledLayout->setName(name() + "_disabledLayout");
}
setState(_currentState);
}
void TeButtonLayout::setHitZone(TeLayout *hitZoneLayout) {
if (_hitZoneLayout)
removeChild(_hitZoneLayout);
_hitZoneLayout = hitZoneLayout;
if (_hitZoneLayout) {
_sizeChanged = true;
addChild(_hitZoneLayout);
//_hitZoneLayout->setColor(TeColor(0, 0, 0xff, 0xff));
//_hitZoneLayout->setName(name() + "_hitZoneLayout");
}
}
void TeButtonLayout::setDownLayout(TeLayout *downLayout) {
if (_downLayout)
removeChild(_downLayout);
if (downLayout)
addChild(downLayout);
_downLayout = downLayout;
if (sizeType() == ABSOLUTE &&
size().x() == 1.0f && size().y() == 1.0f &&
!_upLayout && _downLayout) {
setSize(_downLayout->size());
}
//if (_downLayout) {
// _downLayout->setColor(TeColor(0, 0, 0, 0));
//_downLayout->setName(name() + "_downLayout");
//}
setState(_currentState);
}
void TeButtonLayout::setRollOverLayout(TeLayout *rollOverLayout) {
if (_rolloverLayout)
removeChild(_rolloverLayout);
_rolloverLayout = rollOverLayout;
if (_rolloverLayout) {
addChild(_rolloverLayout);
//_rolloverLayout->setName(name() + "_rolloverLayout");
}
// This is not a copy paste error, or at least, not *my*
// copy paste error.. it's what the original game does.
//if (_disabledLayout)
// _disabledLayout->setColor(TeColor(0, 0, 0, 0));
setState(_currentState);
}
void TeButtonLayout::setUpLayout(TeLayout *upLayout) {
if (_upLayout)
removeChild(_upLayout);
if (upLayout)
addChild(upLayout);
_upLayout = upLayout;
if (sizeType() == ABSOLUTE &&
size().x() == 1.0f && size().y() == 1.0f &&
!_downLayout && _upLayout) {
setSize(_upLayout->size());
}
if (_upLayout) {
//_upLayout->setColor(TeColor(0, 0, 0, 0));
//_upLayout->setName(name() + "_upLayout");
}
setState(_currentState);
}
void TeButtonLayout::setDoubleValidationProtectionEnabled(bool enable) {
_doubleValidationProtectionEnabled = enable;
}
void TeButtonLayout::setEnable(bool enable) {
if (enable && _currentState == BUTTON_STATE_DISABLED) {
_currentState = BUTTON_STATE_UP;
setState(_currentState);
} else if (!enable && _currentState != BUTTON_STATE_DISABLED) {
_currentState = BUTTON_STATE_DISABLED;
setState(_currentState);
}
}
void TeButtonLayout::setPosition(const TeVector3f32 &pos) {
TeLayout::setPosition(pos);
if (_currentState != BUTTON_STATE_DISABLED) {
//int somethingCount = 0;
if (!_intArr.empty()) {
// probably not needed as we reimplememted how this works.
error("TODO: Implement setPosition logic for up/down state");
}
// Original does something like this, but that breaks in
// Amerzone where the button position can move during a click.
//if (!_ignoreMouseEvents) {
// setState(somethingCount ? BUTTON_STATE_DOWN : BUTTON_STATE_UP);
//}
}
}
void TeButtonLayout::setState(State newState) {
if (_currentState != newState) {
switch (newState) {
case BUTTON_STATE_UP:
_onButtonChangedToStateUpSignal.call();
break;
case BUTTON_STATE_DOWN:
_onButtonChangedToStateDownSignal.call();
break;
case BUTTON_STATE_ROLLOVER:
_onButtonChangedToStateRolloverSignal.call();
break;
default:
break;
}
_currentState = newState;
}
if (_upLayout)
_upLayout->setVisible(_currentState == BUTTON_STATE_UP
|| (_currentState == BUTTON_STATE_ROLLOVER && _rolloverLayout == nullptr)
|| (_currentState == BUTTON_STATE_DOWN && _downLayout == nullptr)
|| (_currentState == BUTTON_STATE_DISABLED && _disabledLayout == nullptr));
if (_downLayout)
_downLayout->setVisible(_currentState == BUTTON_STATE_DOWN);
if (_disabledLayout)
_disabledLayout->setVisible(_currentState == BUTTON_STATE_DISABLED);
if (_rolloverLayout)
_rolloverLayout->setVisible(_currentState == BUTTON_STATE_ROLLOVER);
}
} // end namespace Tetraedge