/* 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 .
*
*/
#include "alcachofa/alcachofa.h"
#include "alcachofa/script.h"
#include "alcachofa/global-ui.h"
#include "alcachofa/menu.h"
#include "alcachofa/objects.h"
#include "alcachofa/rooms.h"
using namespace Common;
namespace Alcachofa {
const char *MenuButton::typeName() const { return "MenuButton"; }
MenuButton::MenuButton(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
, _actionId(stream.readSint32LE())
, _graphicNormal(stream)
, _graphicHovered(stream)
, _graphicClicked(stream)
, _graphicDisabled(stream) {}
void MenuButton::draw() {
if (!isEnabled())
return;
Graphic &graphic =
!_isInteractable ? _graphicDisabled
: _isClicked ? _graphicClicked
: wasSelected() ? _graphicHovered
: _graphicNormal;
graphic.update();
g_engine->drawQueue().add(graphic, true, BlendMode::AdditiveAlpha);
}
void MenuButton::update() {
PhysicalObject::update();
if (!_isClicked)
return;
_graphicClicked.update();
if (!_graphicClicked.isPaused())
return;
if (!_triggerNextFrame) {
// another delay probably to show the last frame of animation
_triggerNextFrame = true;
return;
}
_interactionLock.release();
_triggerNextFrame = false;
_isClicked = false;
trigger();
}
void MenuButton::loadResources() {
_graphicNormal.loadResources();
_graphicHovered.loadResources();
_graphicClicked.loadResources();
_graphicDisabled.loadResources();
}
void MenuButton::freeResources() {
_graphicNormal.freeResources();
_graphicHovered.freeResources();
_graphicClicked.freeResources();
_graphicDisabled.freeResources();
}
void MenuButton::onHoverUpdate() {}
void MenuButton::onClick() {
if (_isInteractable && _interactionLock.isReleased()) {
_interactionLock = FakeLock("button", g_engine->menu().interactionSemaphore());
_isClicked = true;
_triggerNextFrame = false;
_graphicClicked.start(false);
}
}
void MenuButton::trigger() {
// all menu buttons should be inherited and override trigger
warning("Unimplemented %s %s action %d", typeName(), name().c_str(), _actionId);
}
const char *InternetMenuButton::typeName() const { return "InternetMenuButton"; }
InternetMenuButton::InternetMenuButton(Room *room, ReadStream &stream)
: MenuButton(room, stream) {}
const char *OptionsMenuButton::typeName() const { return "OptionsMenuButton"; }
OptionsMenuButton::OptionsMenuButton(Room *room, ReadStream &stream)
: MenuButton(room, stream) {}
void OptionsMenuButton::update() {
MenuButton::update();
const auto action = (OptionsMenuAction)actionId();
if (action == OptionsMenuAction::MainMenu && g_engine->input().wasMenuKeyPressed())
onClick();
}
void OptionsMenuButton::trigger() {
g_engine->menu().triggerOptionsAction((OptionsMenuAction)actionId());
}
const char *MainMenuButton::typeName() const { return "MainMenuButton"; }
MainMenuButton::MainMenuButton(Room *room, ReadStream &stream)
: MenuButton(room, stream) {}
void MainMenuButton::update() {
MenuButton::update();
const auto action = (MainMenuAction)actionId();
if (g_engine->input().wasMenuKeyPressed() &&
(action == MainMenuAction::ContinueGame || action == MainMenuAction::NewGame))
onClick();
}
void MainMenuButton::trigger() {
g_engine->menu().triggerMainMenuAction((MainMenuAction)actionId());
}
const char *PushButton::typeName() const { return "PushButton"; }
PushButton::PushButton(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
, _alwaysVisible(readBool(stream))
, _graphic1(stream)
, _graphic2(stream)
, _actionId(stream.readSint32LE()) {}
const char *EditBox::typeName() const { return "EditBox"; }
EditBox::EditBox(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
, i1(stream.readSint32LE())
, p1(Shape(stream).firstPoint())
, _labelId(readVarString(stream))
, b1(readBool(stream))
, i3(stream.readSint32LE())
, i4(stream.readSint32LE())
, i5(stream.readSint32LE())
, _fontId(0) {
if (g_engine->version() == EngineVersion::V3_1)
_fontId = stream.readSint32LE();
}
const char *CheckBox::typeName() const { return "CheckBox"; }
CheckBox::CheckBox(Room *room, ReadStream &stream)
: PhysicalObject(room, stream)
, _isChecked(readBool(stream))
, _graphicUnchecked(stream)
, _graphicChecked(stream)
, _graphicHovered(stream)
, _graphicClicked(stream)
, _actionId(stream.readSint32LE()) {}
void CheckBox::draw() {
if (!isEnabled())
return;
Graphic &baseGraphic = _isChecked ? _graphicChecked : _graphicUnchecked;
baseGraphic.update();
g_engine->drawQueue().add(baseGraphic, true, BlendMode::AdditiveAlpha);
if (wasSelected()) {
Graphic &hoverGraphic = _wasClicked ? _graphicClicked : _graphicHovered;
hoverGraphic.update();
g_engine->drawQueue().add(hoverGraphic, true, BlendMode::AdditiveAlpha);
}
}
void CheckBox::update() {
PhysicalObject::update();
if (_wasClicked) {
if (g_engine->getMillis() - _clickTime > 500) {
_wasClicked = false;
trigger();
}
}
// the original engine would stall the application as click delay.
// this would prevent bacterios arm in movie adventure being rendered twice for multiple checkboxes
// we can instead check the hovered state and prevent the arm (clicked/hovered graphic) being drawn
}
void CheckBox::loadResources() {
_wasClicked = false;
_graphicUnchecked.loadResources();
_graphicChecked.loadResources();
_graphicHovered.loadResources();
_graphicClicked.loadResources();
}
void CheckBox::freeResources() {
_graphicUnchecked.freeResources();
_graphicChecked.freeResources();
_graphicHovered.freeResources();
_graphicClicked.freeResources();
}
void CheckBox::onHoverUpdate() {}
void CheckBox::onClick() {
_wasClicked = true;
_clickTime = g_engine->getMillis();
}
void CheckBox::trigger() {
g_engine->menu().triggerOptionsAction((OptionsMenuAction)actionId());
}
const char *CheckBoxAutoAdjustNoise::typeName() const { return "CheckBoxAutoAdjustNoise"; }
CheckBoxAutoAdjustNoise::CheckBoxAutoAdjustNoise(Room *room, ReadStream &stream)
: CheckBox(room, stream) {
stream.readByte(); // unused and ignored byte
}
const char *SlideButton::typeName() const { return "SlideButton"; }
SlideButton::SlideButton(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _valueId(stream.readSint32LE())
, _minPos(Shape(stream).firstPoint())
, _maxPos(Shape(stream).firstPoint())
, _graphicIdle(stream)
, _graphicHovered(stream)
, _graphicClicked(stream) {}
void SlideButton::draw() {
auto *optionsMenu = dynamic_cast(room());
scumm_assert(optionsMenu != nullptr);
Graphic *activeGraphic;
if (optionsMenu->currentSlideButton() == this && g_engine->input().isMouseLeftDown())
activeGraphic = &_graphicClicked;
else
activeGraphic = isMouseOver() ? &_graphicHovered : &_graphicIdle;
activeGraphic->update();
g_engine->drawQueue().add(*activeGraphic, true, BlendMode::AdditiveAlpha);
}
void SlideButton::update() {
const auto mousePos = g_engine->input().mousePos2D();
auto *optionsMenu = dynamic_cast(room());
scumm_assert(optionsMenu != nullptr);
if (optionsMenu->currentSlideButton() == this) {
if (!g_engine->input().isMouseLeftDown()) {
optionsMenu->currentSlideButton() = nullptr;
g_engine->menu().triggerOptionsValue((OptionsMenuValue)_valueId, _value);
update(); // to update the position
} else {
int clippedMousePosY = CLIP(mousePos.y, _minPos.y, _maxPos.y);
_value = (_maxPos.y - clippedMousePosY) / (float)(_maxPos.y - _minPos.y);
_graphicClicked.topLeft() = Point((_minPos.x + _maxPos.x) / 2, clippedMousePosY);
}
} else {
_graphicIdle.topLeft() = Point(
(_minPos.x + _maxPos.x) / 2,
(int16)(_maxPos.y - _value * (_maxPos.y - _minPos.y)));
if (!isMouseOver())
return;
_graphicHovered.topLeft() = _graphicIdle.topLeft();
if (g_engine->input().wasMouseLeftPressed())
optionsMenu->currentSlideButton() = this;
optionsMenu->clearLastSelectedObject();
g_engine->player().selectedObject() = nullptr;
}
}
void SlideButton::loadResources() {
_graphicIdle.loadResources();
_graphicHovered.loadResources();
_graphicClicked.loadResources();
}
void SlideButton::freeResources() {
_graphicIdle.freeResources();
_graphicHovered.freeResources();
_graphicClicked.freeResources();
}
bool SlideButton::isMouseOver() const {
const auto mousePos = g_engine->input().mousePos2D();
return
mousePos.x >= _minPos.x && mousePos.y >= _minPos.y &&
mousePos.x <= _maxPos.x && mousePos.y <= _maxPos.y;
}
const char *IRCWindow::typeName() const { return "IRCWindow"; }
IRCWindow::IRCWindow(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _p1(Shape(stream).firstPoint())
, _p2(Shape(stream).firstPoint()) {}
const char *MessageBox::typeName() const { return "MessageBox"; }
MessageBox::MessageBox(Room *room, ReadStream &stream)
: ObjectBase(room, stream)
, _graph1(stream)
, _graph2(stream)
, _graph3(stream)
, _graph4(stream)
, _graph5(stream) {
_graph1.start(true);
_graph2.start(true);
_graph3.start(true);
_graph4.start(true);
_graph5.start(true);
}
const char *VoiceMeter::typeName() const { return "VoiceMeter"; }
VoiceMeter::VoiceMeter(Room *room, ReadStream &stream)
: GraphicObject(room, stream) {
stream.readByte(); // unused and ignored byte
}
}