mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-04 05:04:33 +00:00
InputManager: Refactor and simplify vibration mapping
Now multiple devices can be bound if anyone wants to do that for some reason... Current strength will also synchronize on binding reload instead of getting lost.
This commit is contained in:
@@ -271,18 +271,18 @@ void InputBindingWidget::reloadBinding()
|
||||
|
||||
void InputBindingWidget::onClicked()
|
||||
{
|
||||
if (m_bindings.size() > 1)
|
||||
{
|
||||
openDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
if (InputBindingInfo::IsEffectType(m_bind_type))
|
||||
{
|
||||
showEffectBindingDialog();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_bindings.size() > 1)
|
||||
{
|
||||
openDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isListeningForInput())
|
||||
stopListeningForInput();
|
||||
|
||||
@@ -419,13 +419,47 @@ void InputBindingWidget::openDialog()
|
||||
|
||||
void InputBindingWidget::showEffectBindingDialog()
|
||||
{
|
||||
std::vector<InputBindingKey> options;
|
||||
QStringList option_names;
|
||||
QString current;
|
||||
if (!g_emu_thread->getInputDeviceListModel()->hasEffectsOfType(m_bind_type))
|
||||
{
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"),
|
||||
(m_bind_type == InputBindingInfo::Type::Motor) ?
|
||||
tr("No devices with vibration motors were detected.") :
|
||||
tr("No devices with LEDs were detected."));
|
||||
return;
|
||||
}
|
||||
|
||||
const QString full_key(QString::fromStdString(fmt::format("{}/{}", m_section_name, m_key_name)));
|
||||
|
||||
QDialog dlg(this);
|
||||
dlg.setWindowTitle(full_key);
|
||||
dlg.setFixedWidth(450);
|
||||
dlg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
|
||||
QVBoxLayout* const main_layout = new QVBoxLayout(&dlg);
|
||||
|
||||
QHBoxLayout* const heading_layout = new QHBoxLayout();
|
||||
QLabel* const icon = new QLabel(&dlg);
|
||||
icon->setPixmap(QIcon::fromTheme(QStringLiteral("pushpin-line")).pixmap(32, 32));
|
||||
QLabel* const heading =
|
||||
new QLabel(tr("<strong>%1</strong><br>Select the device and effect to map this bind to.").arg(full_key), &dlg);
|
||||
heading->setWordWrap(true);
|
||||
heading_layout->addWidget(icon, 0, Qt::AlignTop | Qt::AlignLeft);
|
||||
heading_layout->addWidget(heading, 1);
|
||||
main_layout->addLayout(heading_layout);
|
||||
|
||||
QListWidget* const list = new QListWidget(&dlg);
|
||||
list->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
// hook up selection to alter check state
|
||||
connect(list, &QListWidget::itemSelectionChanged, [list]() {
|
||||
const int count = list->count();
|
||||
for (int i = 0; i < count; i++)
|
||||
list->item(i)->setCheckState(Qt::Unchecked);
|
||||
|
||||
for (QListWidgetItem* item : list->selectedItems())
|
||||
item->setCheckState(item->isSelected() ? Qt::Checked : Qt::Unchecked);
|
||||
});
|
||||
|
||||
const InputDeviceListModel::EffectList& all_options = g_emu_thread->getInputDeviceListModel()->getEffectList();
|
||||
options.reserve(all_options.size());
|
||||
option_names.reserve(all_options.size());
|
||||
for (const auto& [type, key] : g_emu_thread->getInputDeviceListModel()->getEffectList())
|
||||
{
|
||||
if (type != m_bind_type)
|
||||
@@ -435,47 +469,57 @@ void InputBindingWidget::showEffectBindingDialog()
|
||||
if (name.empty())
|
||||
continue;
|
||||
|
||||
QString qname = QtUtils::StringViewToQString(name);
|
||||
if (!m_bindings.empty() && name == m_bindings.front())
|
||||
current = qname;
|
||||
const bool is_bound =
|
||||
std::ranges::any_of(m_bindings, [&name](const std::string& other_name) { return (other_name == name.view()); });
|
||||
|
||||
options.push_back(key);
|
||||
option_names.push_back(std::move(qname));
|
||||
QListWidgetItem* const item = new QListWidgetItem();
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(is_bound ? Qt::Checked : Qt::Unchecked);
|
||||
item->setText(QStringLiteral("%1\n%2")
|
||||
.arg(QtUtils::StringViewToQString(name))
|
||||
.arg(g_emu_thread->getInputDeviceListModel()->getDeviceName(key)));
|
||||
item->setData(Qt::UserRole, QtUtils::StringViewToQString(name));
|
||||
item->setIcon(InputDeviceListModel::getIconForKey(key));
|
||||
list->addItem(item);
|
||||
|
||||
item->setSelected(is_bound);
|
||||
}
|
||||
|
||||
if (options.empty())
|
||||
{
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"),
|
||||
(m_bind_type == InputBindingInfo::Type::Motor) ?
|
||||
tr("No devices with vibration motors were detected.") :
|
||||
tr("No devices with LEDs were detected."));
|
||||
return;
|
||||
}
|
||||
main_layout->addWidget(list);
|
||||
|
||||
// TODO: Multiple options? needs a custom dialog
|
||||
const QString full_key(
|
||||
QStringLiteral("%1/%2").arg(QString::fromStdString(m_section_name)).arg(QString::fromStdString(m_key_name)));
|
||||
QInputDialog input_dialog(this);
|
||||
input_dialog.setWindowTitle(full_key);
|
||||
input_dialog.setLabelText(tr("Select device and effect for %1.").arg(full_key));
|
||||
input_dialog.setInputMode(QInputDialog::TextInput);
|
||||
input_dialog.setOptions(QInputDialog::UseListViewForComboBoxItems);
|
||||
input_dialog.setComboBoxEditable(false);
|
||||
input_dialog.setComboBoxItems(option_names);
|
||||
input_dialog.setTextValue(current);
|
||||
if (input_dialog.exec() == QDialog::Rejected)
|
||||
QDialogButtonBox* const bbox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
|
||||
connect(bbox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
|
||||
connect(bbox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
|
||||
main_layout->addWidget(bbox);
|
||||
|
||||
if (dlg.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
const QString new_value = input_dialog.textValue();
|
||||
for (qsizetype i = 0; i < option_names.size(); i++)
|
||||
m_bindings.clear();
|
||||
|
||||
const int count = list->count();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (new_value == option_names[i])
|
||||
{
|
||||
m_new_bindings.clear();
|
||||
m_new_bindings.push_back(options[i]);
|
||||
setNewBinding();
|
||||
reloadBinding();
|
||||
return;
|
||||
}
|
||||
const QListWidgetItem* const item = list->item(i);
|
||||
if (item->checkState() == Qt::Checked)
|
||||
m_bindings.push_back(item->data(Qt::UserRole).toString().toStdString());
|
||||
}
|
||||
|
||||
if (m_sif)
|
||||
{
|
||||
m_sif->SetStringList(m_section_name.c_str(), m_key_name.c_str(), m_bindings);
|
||||
QtHost::SaveGameSettings(m_sif, false);
|
||||
g_emu_thread->reloadGameSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::SetBaseStringListSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_bindings);
|
||||
Host::CommitBaseSettingChanges();
|
||||
if (m_bind_type == InputBindingInfo::Type::Pointer)
|
||||
g_emu_thread->updateControllerSettings();
|
||||
g_emu_thread->reloadInputBindings();
|
||||
}
|
||||
|
||||
setNewBinding();
|
||||
reloadBinding();
|
||||
}
|
||||
|
||||
@@ -2543,6 +2543,26 @@ QIcon InputDeviceListModel::getIconForKey(const InputBindingKey& key)
|
||||
return QIcon::fromTheme(QStringLiteral("controller-line"));
|
||||
}
|
||||
|
||||
QString InputDeviceListModel::getDeviceName(const InputBindingKey& key)
|
||||
{
|
||||
QString ret;
|
||||
for (const InputDeviceListModel::Device& device : m_devices)
|
||||
{
|
||||
if (device.key.source_type == key.source_type && device.key.source_index == key.source_index)
|
||||
{
|
||||
ret = device.display_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool InputDeviceListModel::hasEffectsOfType(InputBindingInfo::Type type)
|
||||
{
|
||||
return std::ranges::any_of(m_effects, [type](const auto& eff) { return eff.first == type; });
|
||||
}
|
||||
|
||||
int InputDeviceListModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const
|
||||
{
|
||||
return m_devices.size();
|
||||
|
||||
@@ -287,6 +287,12 @@ public:
|
||||
ALWAYS_INLINE const DeviceList& getDeviceList() const { return m_devices; }
|
||||
ALWAYS_INLINE const EffectList& getEffectList() const { return m_effects; }
|
||||
|
||||
/// Returns the device name for the specified key, or an empty string if not found.
|
||||
QString getDeviceName(const InputBindingKey& key);
|
||||
|
||||
/// Returns whether any effects are available for the specified type.
|
||||
bool hasEffectsOfType(InputBindingInfo::Type type);
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
<file>icons/black/svg/play-line.svg</file>
|
||||
<file>icons/black/svg/play-list-2-line.svg</file>
|
||||
<file>icons/black/svg/price-tag-3-line.svg</file>
|
||||
<file>icons/black/svg/pushpin-line.svg</file>
|
||||
<file>icons/black/svg/refresh-line.svg</file>
|
||||
<file>icons/black/svg/restart-line.svg</file>
|
||||
<file>icons/black/svg/save-3-line.svg</file>
|
||||
@@ -320,6 +321,7 @@
|
||||
<file>icons/white/svg/play-line.svg</file>
|
||||
<file>icons/white/svg/play-list-2-line.svg</file>
|
||||
<file>icons/white/svg/price-tag-3-line.svg</file>
|
||||
<file>icons/white/svg/pushpin-line.svg</file>
|
||||
<file>icons/white/svg/refresh-line.svg</file>
|
||||
<file>icons/white/svg/restart-line.svg</file>
|
||||
<file>icons/white/svg/save-3-line.svg</file>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000"><path d="M13.8273 1.69L22.3126 10.1753L20.8984 11.5895L20.1913 10.8824L15.9486 15.125L15.2415 18.6606L13.8273 20.0748L9.58466 15.8321L4.63492 20.7819L3.2207 19.3677L8.17045 14.4179L3.92781 10.1753L5.34202 8.76107L8.87756 8.05396L13.1202 3.81132L12.4131 3.10422L13.8273 1.69ZM14.5344 5.22554L9.86358 9.89637L7.0417 10.4607L13.5418 16.9609L14.1062 14.139L18.7771 9.46818L14.5344 5.22554Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 475 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ffffff"><path d="M13.8273 1.69L22.3126 10.1753L20.8984 11.5895L20.1913 10.8824L15.9486 15.125L15.2415 18.6606L13.8273 20.0748L9.58466 15.8321L4.63492 20.7819L3.2207 19.3677L8.17045 14.4179L3.92781 10.1753L5.34202 8.76107L8.87756 8.05396L13.1202 3.81132L12.4131 3.10422L13.8273 1.69ZM14.5344 5.22554L9.86358 9.89637L7.0417 10.4607L13.5418 16.9609L14.1062 14.139L18.7771 9.46818L14.5344 5.22554Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 475 B |
@@ -68,34 +68,19 @@ struct InputBinding
|
||||
|
||||
struct PadVibrationBinding
|
||||
{
|
||||
struct Motor
|
||||
{
|
||||
InputBindingKey binding;
|
||||
Timer::Value last_update_time;
|
||||
InputSource* source;
|
||||
u32 bind_index;
|
||||
float last_intensity;
|
||||
};
|
||||
|
||||
u32 pad_index = 0;
|
||||
Motor motors[MAX_MOTORS_PER_PAD] = {};
|
||||
|
||||
/// Returns true if the two motors are bound to the same host motor.
|
||||
ALWAYS_INLINE bool AreMotorsCombined() const { return motors[0].binding == motors[1].binding; }
|
||||
|
||||
/// Returns the intensity when both motors are combined.
|
||||
ALWAYS_INLINE float GetCombinedIntensity() const
|
||||
{
|
||||
return std::max(motors[0].last_intensity, motors[1].last_intensity);
|
||||
}
|
||||
u64 pad_and_bind_index; ///< Combined pad index and bind index for quick lookup.
|
||||
InputBindingKey binding; ///< Binding key for this motor.
|
||||
Timer::Value last_update_time; ///< Last time this motor was updated.
|
||||
InputSource* source; ///< Input source for this motor.
|
||||
float last_intensity; ///< Last intensity we sent to the motor.
|
||||
};
|
||||
|
||||
struct PadLEDBinding
|
||||
{
|
||||
InputBindingKey binding;
|
||||
InputSource* source;
|
||||
float last_intensity;
|
||||
u32 pad_index;
|
||||
InputBindingKey binding; ///< Binding key for this LED.
|
||||
InputSource* source; ///< Input source for this LED.
|
||||
float last_intensity; ///< Last intensity we sent to the LED.
|
||||
u32 pad_index; ///< Pad index this LED is for.
|
||||
};
|
||||
|
||||
struct MacroButton
|
||||
@@ -164,6 +149,11 @@ static void UpdateInputSourceState(const SettingsInterface& si, std::unique_lock
|
||||
|
||||
static const KeyCodeData* FindKeyCodeData(u32 usb_code);
|
||||
|
||||
ALWAYS_INLINE static u64 PackPadAndBindIndex(u32 pad_index, u32 bind_index)
|
||||
{
|
||||
return (static_cast<u64>(pad_index) << 32) | static_cast<u64>(bind_index);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Tracking host mouse movement and turning into relative events
|
||||
// 4 axes: pointer left/right, wheel vertical/horizontal. Last/Next/Normalized.
|
||||
@@ -623,23 +613,15 @@ void InputManager::AddBinding(std::string_view binding, const InputEventHandler&
|
||||
s_state.binding_map.emplace(ibinding->keys[i].MaskDirection(), ibinding);
|
||||
}
|
||||
|
||||
void InputManager::AddVibrationBinding(u32 pad_index, const InputBindingKey* motor_0_binding,
|
||||
InputSource* motor_0_source, const InputBindingKey* motor_1_binding,
|
||||
InputSource* motor_1_source)
|
||||
void InputManager::AddVibrationBinding(u32 pad_index, u32 bind_index, const InputBindingKey& binding,
|
||||
InputSource* source)
|
||||
{
|
||||
PadVibrationBinding vib;
|
||||
vib.pad_index = pad_index;
|
||||
if (motor_0_binding)
|
||||
{
|
||||
vib.motors[0].binding = *motor_0_binding;
|
||||
vib.motors[0].source = motor_0_source;
|
||||
}
|
||||
if (motor_1_binding)
|
||||
{
|
||||
vib.motors[1].binding = *motor_1_binding;
|
||||
vib.motors[1].source = motor_1_source;
|
||||
}
|
||||
s_state.pad_vibration_array.push_back(std::move(vib));
|
||||
s_state.pad_vibration_array.push_back(
|
||||
PadVibrationBinding{.pad_and_bind_index = PackPadAndBindIndex(pad_index, bind_index),
|
||||
.binding = binding,
|
||||
.last_update_time = 0,
|
||||
.source = source,
|
||||
.last_intensity = 0.0f});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -965,12 +947,6 @@ void InputManager::AddHotkeyBindings(const SettingsInterface& si)
|
||||
void InputManager::AddPadBindings(const SettingsInterface& si, const std::string& section, u32 pad_index,
|
||||
const Controller::ControllerInfo& cinfo)
|
||||
{
|
||||
bool vibration_binding_valid = false;
|
||||
PadVibrationBinding vibration_binding = {};
|
||||
vibration_binding.pad_index = pad_index;
|
||||
PadLEDBinding led_binding = {};
|
||||
led_binding.pad_index = pad_index;
|
||||
|
||||
for (const Controller::ControllerBindingInfo& bi : cinfo.bindings)
|
||||
{
|
||||
const std::vector<std::string> bindings(si.GetStringList(section.c_str(), bi.name));
|
||||
@@ -1032,20 +1008,25 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string
|
||||
|
||||
case InputBindingInfo::Type::Motor:
|
||||
{
|
||||
DebugAssert(bi.generic_mapping == GenericInputBinding::LargeMotor ||
|
||||
bi.generic_mapping == GenericInputBinding::SmallMotor);
|
||||
if (bindings.empty())
|
||||
continue;
|
||||
|
||||
if (bindings.size() > 1)
|
||||
WARNING_LOG("More than one vibration motor binding for {}:{}", pad_index, bi.name);
|
||||
|
||||
PadVibrationBinding::Motor& motor =
|
||||
vibration_binding.motors[BoolToUInt32(bi.generic_mapping == GenericInputBinding::SmallMotor)];
|
||||
if (ParseBindingAndGetSource(bindings.front(), &motor.binding, &motor.source))
|
||||
for (const std::string& binding : bindings)
|
||||
{
|
||||
motor.bind_index = bi.bind_index;
|
||||
vibration_binding_valid = true;
|
||||
PadVibrationBinding vib_binding;
|
||||
if (ParseBindingAndGetSource(binding, &vib_binding.binding, &vib_binding.source))
|
||||
{
|
||||
vib_binding.pad_and_bind_index = PackPadAndBindIndex(pad_index, bi.bind_index);
|
||||
vib_binding.last_update_time = 0;
|
||||
|
||||
// If we're reloading bindings due to e.g. device connection, sync the vibration state.
|
||||
if (Controller* controller = System::GetController(pad_index))
|
||||
vib_binding.last_intensity = controller->GetBindState(bi.bind_index);
|
||||
else
|
||||
vib_binding.last_intensity = 0.0f;
|
||||
|
||||
s_state.pad_vibration_array.push_back(vib_binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1055,16 +1036,24 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string
|
||||
if (bindings.empty())
|
||||
continue;
|
||||
|
||||
if (ParseBindingAndGetSource(bindings.front(), &led_binding.binding, &led_binding.source))
|
||||
for (const std::string& binding : bindings)
|
||||
{
|
||||
// If we're reloading bindings due to e.g. device connection, sync the LED state.
|
||||
if (Controller* controller = System::GetController(pad_index))
|
||||
led_binding.last_intensity = controller->GetBindState(bi.bind_index);
|
||||
PadLEDBinding led_binding;
|
||||
if (ParseBindingAndGetSource(binding, &led_binding.binding, &led_binding.source))
|
||||
{
|
||||
led_binding.pad_index = pad_index;
|
||||
|
||||
// Need to pass it through unconditionally, otherwise if the LED was on it'll stay on.
|
||||
led_binding.source->UpdateLEDState(led_binding.binding, led_binding.last_intensity);
|
||||
// If we're reloading bindings due to e.g. device connection, sync the LED state.
|
||||
if (Controller* controller = System::GetController(pad_index))
|
||||
led_binding.last_intensity = controller->GetBindState(bi.bind_index);
|
||||
else
|
||||
led_binding.last_intensity = 0.0f;
|
||||
|
||||
s_state.pad_led_array.push_back(std::move(led_binding));
|
||||
// Need to pass it through unconditionally, otherwise if the LED was on it'll stay on.
|
||||
led_binding.source->UpdateLEDState(led_binding.binding, led_binding.last_intensity);
|
||||
|
||||
s_state.pad_led_array.push_back(led_binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1079,9 +1068,6 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vibration_binding_valid)
|
||||
s_state.pad_vibration_array.push_back(std::move(vibration_binding));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -1833,25 +1819,14 @@ std::unique_ptr<ForceFeedbackDevice> InputManager::CreateForceFeedbackDevice(con
|
||||
|
||||
void InputManager::SetPadVibrationIntensity(u32 pad_index, u32 bind_index, float intensity)
|
||||
{
|
||||
for (PadVibrationBinding& pad : s_state.pad_vibration_array)
|
||||
const u64 pad_and_bind_index = PackPadAndBindIndex(pad_index, bind_index);
|
||||
for (PadVibrationBinding& vib : s_state.pad_vibration_array)
|
||||
{
|
||||
if (pad.pad_index != pad_index)
|
||||
continue;
|
||||
|
||||
PadVibrationBinding::Motor* motor;
|
||||
if (bind_index == pad.motors[0].bind_index)
|
||||
motor = &pad.motors[0];
|
||||
else if (bind_index == pad.motors[1].bind_index)
|
||||
motor = &pad.motors[1];
|
||||
else
|
||||
break;
|
||||
|
||||
if (motor->last_intensity == intensity)
|
||||
break;
|
||||
|
||||
motor->last_intensity = intensity;
|
||||
motor->last_update_time = 0; // force update at end of frame
|
||||
break;
|
||||
if (vib.pad_and_bind_index == pad_and_bind_index && vib.last_intensity != intensity)
|
||||
{
|
||||
vib.last_intensity = intensity;
|
||||
vib.last_update_time = 0; // force update at end of frame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1859,16 +1834,9 @@ void InputManager::PauseVibration()
|
||||
{
|
||||
for (PadVibrationBinding& binding : s_state.pad_vibration_array)
|
||||
{
|
||||
for (u32 motor_index = 0; motor_index < MAX_MOTORS_PER_PAD; motor_index++)
|
||||
{
|
||||
PadVibrationBinding::Motor& motor = binding.motors[motor_index];
|
||||
if (!motor.source || motor.last_intensity == 0.0f)
|
||||
continue;
|
||||
|
||||
// we deliberately don't zero the intensity here, so it can resume later
|
||||
motor.last_update_time = 0;
|
||||
motor.source->UpdateMotorState(motor.binding, 0.0f);
|
||||
}
|
||||
// we deliberately don't zero the intensity here, so it can resume later
|
||||
binding.last_update_time = 0;
|
||||
binding.source->UpdateMotorState(binding.binding, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1876,66 +1844,50 @@ void InputManager::UpdateContinuedVibration()
|
||||
{
|
||||
// update vibration intensities, so if the game does a long effect, it continues
|
||||
const u64 current_time = Timer::GetCurrentValue();
|
||||
for (PadVibrationBinding& pad : s_state.pad_vibration_array)
|
||||
for (PadVibrationBinding& binding : s_state.pad_vibration_array)
|
||||
{
|
||||
if (pad.AreMotorsCombined())
|
||||
// skip if motor is off and wasn't just changed
|
||||
if (binding.last_update_time > 0)
|
||||
{
|
||||
// motors are combined
|
||||
PadVibrationBinding::Motor& large_motor = pad.motors[0];
|
||||
PadVibrationBinding::Motor& small_motor = pad.motors[1];
|
||||
|
||||
// skip if both motors are off and this wasn't just changed
|
||||
const Timer::Value min_update_time = std::min(large_motor.last_update_time, small_motor.last_update_time);
|
||||
const double dt = Timer::ConvertValueToSeconds(current_time - min_update_time);
|
||||
if (dt < VIBRATION_UPDATE_INTERVAL_SECONDS)
|
||||
continue;
|
||||
|
||||
// but take max of both motors for the intensity
|
||||
const float intensity = pad.GetCombinedIntensity();
|
||||
if (intensity == 0.0f && min_update_time > 0)
|
||||
continue;
|
||||
|
||||
large_motor.last_update_time = current_time;
|
||||
large_motor.source->UpdateMotorState(large_motor.binding, intensity);
|
||||
}
|
||||
else if (pad.motors[0].source && pad.motors[0].source == pad.motors[1].source)
|
||||
{
|
||||
// motors are independent, but share the same source. do a combined update
|
||||
PadVibrationBinding::Motor& large_motor = pad.motors[0];
|
||||
PadVibrationBinding::Motor& small_motor = pad.motors[1];
|
||||
const Timer::Value min_update_time = std::min(large_motor.last_update_time, small_motor.last_update_time);
|
||||
|
||||
// skip if both motors are off and this wasn't just changed
|
||||
if (std::max(large_motor.last_intensity, small_motor.last_intensity) == 0.0f && min_update_time > 0)
|
||||
continue;
|
||||
|
||||
const double dt = Timer::ConvertValueToSeconds(current_time - min_update_time);
|
||||
if (dt < VIBRATION_UPDATE_INTERVAL_SECONDS)
|
||||
continue;
|
||||
|
||||
large_motor.last_update_time = current_time;
|
||||
small_motor.last_update_time = current_time;
|
||||
large_motor.source->UpdateMotorState(large_motor.binding, small_motor.binding, large_motor.last_intensity,
|
||||
small_motor.last_intensity);
|
||||
}
|
||||
else
|
||||
{
|
||||
// independent motor control
|
||||
for (u32 i = 0; i < MAX_MOTORS_PER_PAD; i++)
|
||||
if (binding.last_intensity == 0.0f ||
|
||||
Timer::ConvertValueToSeconds(current_time - binding.last_update_time) < VIBRATION_UPDATE_INTERVAL_SECONDS)
|
||||
{
|
||||
PadVibrationBinding::Motor& motor = pad.motors[i];
|
||||
if (!motor.source || (motor.last_intensity == 0.0f && motor.last_update_time > 0))
|
||||
continue;
|
||||
|
||||
const double dt = Timer::ConvertValueToSeconds(current_time - motor.last_update_time);
|
||||
if (dt < VIBRATION_UPDATE_INTERVAL_SECONDS)
|
||||
continue;
|
||||
|
||||
// re-notify the source of the continued effect
|
||||
motor.last_update_time = current_time;
|
||||
motor.source->UpdateMotorState(motor.binding, motor.last_intensity);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// figure out the intensity, we need to search all bindings since it may be combined
|
||||
// merge into a single update where possible
|
||||
std::array<InputBindingKey, 2> motor_keys = {};
|
||||
std::array<float, 2> motor_intensities = {0.0f, 0.0f};
|
||||
u32 motor_intensities_mask = 0;
|
||||
for (PadVibrationBinding& other_binding : s_state.pad_vibration_array)
|
||||
{
|
||||
// only try to merge devices of the same source/index
|
||||
if (other_binding.source != binding.source || other_binding.binding.source_index != binding.binding.source_index)
|
||||
continue;
|
||||
|
||||
// data should probably never be more than 1, but just in case
|
||||
if (other_binding.binding.data > 1 && binding.binding.data != other_binding.binding.data)
|
||||
continue;
|
||||
|
||||
const u32 motor_index = std::min<u32>(other_binding.binding.data, 1u);
|
||||
other_binding.last_update_time = current_time;
|
||||
motor_keys[motor_index] = other_binding.binding;
|
||||
motor_intensities[motor_index] = std::max(motor_intensities[motor_index], other_binding.last_intensity);
|
||||
motor_intensities_mask |= (1u << motor_index);
|
||||
}
|
||||
|
||||
// can we send it as a single update?
|
||||
DEV_COLOR_LOG(StrongOrange, "Sending vibration update to device {}: mask 0x{:02X}, intensities {{{}, {}}}",
|
||||
static_cast<u32>(binding.binding.source_index), motor_intensities_mask, motor_intensities[0],
|
||||
motor_intensities[1]);
|
||||
if (motor_intensities_mask == 0b11)
|
||||
binding.source->UpdateMotorState(motor_keys[0], motor_keys[1], motor_intensities[0], motor_intensities[1]);
|
||||
else if (motor_intensities_mask == 0b01)
|
||||
binding.source->UpdateMotorState(motor_keys[0], motor_intensities[0]);
|
||||
else // if (motor_intensities_mask == 0b10)
|
||||
binding.source->UpdateMotorState(motor_keys[1], motor_intensities[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -321,8 +321,7 @@ bool ParseBindingAndGetSource(std::string_view binding, InputBindingKey* key, In
|
||||
void AddBinding(std::string_view binding, const InputEventHandler& handler);
|
||||
|
||||
/// Adds an external vibration binding.
|
||||
void AddVibrationBinding(u32 pad_index, const InputBindingKey* motor_0_binding, InputSource* motor_0_source,
|
||||
const InputBindingKey* motor_1_binding, InputSource* motor_1_source);
|
||||
void AddVibrationBinding(u32 pad_index, u32 bind_index, const InputBindingKey& binding, InputSource* source);
|
||||
|
||||
/// Updates internal state for any binds for this key, and fires callbacks as needed.
|
||||
/// Returns true if anything was bound to this key, otherwise false.
|
||||
|
||||
Reference in New Issue
Block a user