1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
//
//
#include "SoundPlugin.h"
#include "gamesnd/gamesnd.h"
#include "utils/string_utils.h"
using namespace Rocket::Core;
namespace {
struct SoundDefinition {
interface_snd_id default_id;
SCP_unordered_map<SCP_string, interface_snd_id> state_ids;
};
SoundDefinition parse_sound_value(const String& value)
{
SoundDefinition def{};
bool saw_default = false;
for (auto& part : util::split_string(SCP_string(value.CString()), ',')) {
drop_white_space(part);
if (part.find(')') != SCP_string::npos) {
auto state_start = part.find('(');
auto state_end = part.find(')');
if (state_start == SCP_string::npos || state_end == SCP_string::npos) {
throw std::runtime_error("Could not find both '(' and ')' for state sound!");
}
state_start += 1; // skip the (
if (state_start > state_end) {
throw std::runtime_error("')' appeared before '('!");
}
SCP_string state_name = part.substr(state_start, state_end - state_start);
if (state_name.empty()) {
throw std::runtime_error("State name is empty!");
}
auto sound_name = part.substr(state_end + 1);
drop_white_space(sound_name);
auto snd = gamesnd_get_by_iface_name(sound_name.c_str());
if (!snd.isValid()) {
SCP_string error("Sound '");
error += sound_name;
error += "' for state '";
error += state_name;
error += "' is invalid!";
throw std::runtime_error(error);
}
def.state_ids.emplace(state_name, snd);
} else {
if (saw_default) {
throw std::runtime_error("Multiple default sounds specified!");
}
def.default_id = gamesnd_get_by_iface_name(part.c_str());
saw_default = true;
if (!def.default_id.isValid()) {
throw std::runtime_error("Default sound has an invalid name!");
}
}
}
return def;
}
}
namespace scpui {
bool SoundPropertyParser::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const
{
try {
parse_sound_value(value);
} catch (const std::exception& e) {
mprintf(("libRocket: Failure to parse sound CSS property '%s': %s\n", value.CString(), e.what()));
return false;
}
property.value = Variant(value);
property.unit = Property::STRING;
return true;
}
void SoundPropertyParser::Release() { delete this; }
int SoundPlugin::GetEventClasses() { return EVT_BASIC | EVT_ELEMENT; }
SoundPlugin* SoundPlugin::_instance = nullptr;
SCP_vector<SCP_string> SoundPlugin::_event_types = {"click", "dblclick", "mouseover", "mouseout",
"mouseup", "mousedown", "mousescroll", "scrollchange"};
const SCP_vector<SCP_string>& SoundPlugin::getEventTypes() { return _event_types; }
void SoundPlugin::OnShutdown() { delete this; }
void SoundPlugin::OnElementCreate(Element* element)
{
for (auto& type : getEventTypes()) {
element->AddEventListener(type.c_str(), _event_handler.get());
}
}
void SoundPlugin::OnElementDestroy(Element* /*element*/) {}
SoundPlugin::SoundPlugin()
{
_event_handler.reset(new SoundEventHandler());
_instance = this;
}
SoundPlugin::~SoundPlugin() { _instance = nullptr; }
SoundPlugin* SoundPlugin::instance() { return _instance; }
bool SoundPlugin::PlayElementSound(Element* element, const String& event, const String& state)
{
auto property_name = event + "-sound";
const Property* sound_prop = element->GetProperty(property_name);
if (!(sound_prop->unit & Property::STRING)) {
// sounds are stored as their original string values which are then parsed whenever necessary
return false;
}
auto sound = sound_prop->Get<String>();
try {
auto snds = parse_sound_value(sound);
interface_snd_id sound_id;
if (state.Empty()) {
sound_id = snds.default_id;
} else {
auto snd_iter = snds.state_ids.find(SCP_string(state.CString()));
if (snd_iter == snds.state_ids.end()) {
return false;
}
sound_id = snd_iter->second;
}
// The default sound is optional so this might be invalid
if (!sound_id.isValid()) {
return false;
}
if (event == "scrollchange") {
if (_last_scrollchange_time >= 0) {
auto diff = Rocket::Core::GetSystemInterface()->GetElapsedTime() - _last_scrollchange_time;
if (diff < gamesnd_get_max_duration(gamesnd_get_interface_sound(sound_id)) / 1000.0f * 1.1f) {
return false;
}
}
_last_scrollchange_time = Rocket::Core::GetSystemInterface()->GetElapsedTime();
}
gamesnd_play_iface(sound_id);
return true;
} catch (const std::exception& e) {
mprintf(("libRocket: Failure to parse sound CSS property '%s': %s\n", sound.CString(), e.what()));
return false;
}
}
void SoundPlugin::SoundEventHandler::ProcessEvent(Event& event)
{
// Hover events bubble up so ignore any mouse movement events that aren't caused by the target element
if ((event.GetType() != "mouseover" && event.GetType() != "mouseout") || event.GetPhase() == Event::PHASE_TARGET) {
SoundPlugin::instance()->PlayElementSound(event.GetCurrentElement(), event.GetType());
}
}
} // namespace scpui
|