diff options
author | Alexander Gavriliuk <alexandrgproductengine@lindenlab.com> | 2024-05-08 23:32:58 +0200 |
---|---|---|
committer | Andrew Meadows <andrew.l.meadows@gmail.com> | 2024-10-03 09:02:09 -0700 |
commit | 2daf175650cdda7cc8f820b6cb17b1475496e7ac (patch) | |
tree | 0ece9bb592a922fbcb3f4532aee10941e307f44f | |
parent | ec39ac89e8529da206dafd519d75ad5944888076 (diff) |
Add GameControl UI for per device settings
31 files changed, 3030 insertions, 1122 deletions
diff --git a/autobuild.xml b/autobuild.xml index 2d06a159df..a1af9879fc 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -1538,7 +1538,7 @@ <key>name</key> <string>linux64</string> </map> - <key>windows</key> + <key>windows64</key> <map> <key>archive</key> <map> @@ -1548,7 +1548,7 @@ <string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60043/564063/llphysicsextensions_stub-1.0.542456-windows-542456.tar.bz2</string> </map> <key>name</key> - <string>windows</string> + <string>windows64</string> </map> </map> <key>license</key> diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h index c6a8dd737e..170a171502 100644 --- a/indra/llcommon/llinitparam.h +++ b/indra/llcommon/llinitparam.h @@ -1771,8 +1771,13 @@ namespace LLInitParam // implicit conversion operator const container_t&() const { return mValues; } + operator container_t&() { return mValues; } // explicit conversion const container_t& operator()() const { return mValues; } + container_t& operator()() { return mValues; } + // direct Nth item access + const typename NAME_VALUE_LOOKUP::type_value_t& operator()(size_t index) const { return mValues[index]; } + typename NAME_VALUE_LOOKUP::type_value_t& operator()(size_t index) { return mValues[index]; } iterator begin() { return mValues.begin(); } iterator end() { return mValues.end(); } @@ -2102,13 +2107,13 @@ namespace LLInitParam class Multiple : public TypedParam<T, NAME_VALUE_LOOKUP, true> { typedef TypedParam<T, NAME_VALUE_LOOKUP, true> super_t; - typedef Multiple<T, RANGE, NAME_VALUE_LOOKUP> self_t; - typedef typename super_t::container_t container_t; + typedef Multiple<T, RANGE, NAME_VALUE_LOOKUP> self_t; + typedef typename super_t::container_t container_t; typedef typename super_t::value_t value_t; public: - typedef typename super_t::iterator iterator; - typedef typename super_t::const_iterator const_iterator; + typedef typename super_t::iterator iterator; + typedef typename super_t::const_iterator const_iterator; using super_t::operator(); using super_t::operator const container_t&; diff --git a/indra/llcommon/llsd.cpp b/indra/llcommon/llsd.cpp index 77fe545c3f..1058ab385b 100644 --- a/indra/llcommon/llsd.cpp +++ b/indra/llcommon/llsd.cpp @@ -974,6 +974,31 @@ void LLSD::clear() { Impl::assignUndefined(impl); } LLSD::Type LLSD::type() const { return safe(impl).type(); } +bool LLSD::isEmpty() const +{ + switch (type()) + { + case TypeUndefined: + return true; // Always empty + case TypeBinary: + return !asBoolean(); // Empty when default + case TypeInteger: + return !asInteger(); // Empty when default + case TypeReal: + return !asReal(); // Empty when default + case TypeString: + case TypeURI: + return asString().empty(); // Empty natively + case TypeArray: + case TypeMap: + return !size(); // Empty natively + default:; + } + // All other value types (TypeDate) don't have default values so can't be empty + return false; +} + + // Scalar Constructors LLSD::LLSD(Boolean v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } LLSD::LLSD(Integer v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); } diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index d2b3548831..7fa193e035 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -335,6 +335,20 @@ public: { return c ? (*this)[std::string_view(c)] : *this; } + + template<typename T> + LLSD(const std::map<String, T>& map, bool exclude_empty = false) + { + assign(emptyMap()); + for (const std::pair<String, T>& pair : map) + { + LLSD value(pair.second); + if (!exclude_empty || !value.isEmpty()) + { + insert(pair.first, value); + } + } + } //@} /** @name Array Values */ @@ -420,6 +434,7 @@ public: bool isBinary() const { return type() == TypeBinary; } bool isMap() const { return type() == TypeMap; } bool isArray() const { return type() == TypeArray; } + bool isEmpty() const; //@} /** @name Automatic Cast Protection diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 447fa2d9dd..e38622b43b 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -1144,18 +1144,27 @@ void LLStringOps::setupDayFormat(const std::string& data) } -std::string LLStringOps::getDatetimeCode (std::string key) +std::string LLStringOps::getDatetimeCode(std::string key) { - std::map<std::string, std::string>::iterator iter; + std::map<std::string, std::string>::iterator iter = datetimeToCodes.find(key); + return iter == datetimeToCodes.end() ? LLStringUtil::null : iter->second; +} - iter = datetimeToCodes.find (key); - if (iter != datetimeToCodes.end()) +void LLStringOps::splitString(const std::string& text, char delimiter, + std::function<void(const std::string&)> handler) +{ + std::size_t from = 0; + for (std::size_t i = 0; i < text.size(); ++i) { - return iter->second; + if (text[i] == delimiter) + { + handler(text.substr(from, i - from)); + from = i + 1; + } } - else + if (from <= text.size()) { - return std::string(""); + handler(text.substr(from)); } } diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index d65fb16f5b..7d0806b22e 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -214,6 +214,9 @@ public: static std::string getDatetimeCode (std::string key); + static void splitString(const std::string& text, char delimiter, + std::function<void(const std::string&)> handler); + // Express a value like 1234567 as "1.23M" static std::string getReadableNumber(F64 num); }; @@ -443,6 +446,65 @@ public: static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); } + // Join non-empty strings from values using value itself and delimiter + template<class C> + static std::string join(const C& values, T delimiter = ',') + { + std::string result; + for (const std::string& value : values) + { + if (!value.empty()) + { + if (!result.empty()) + { + result += delimiter; + } + result += value; + } + } + return result; + } + + // Join non-empty strings from values using stringify(value) and delimiter + template<class C, class V> + static std::string join(const C& values, std::function<std::string(const V&)> stringify, T delimiter = ',') + { + std::string result; + for (const V& value : values) + { + std::string string = stringify(value); + if (!string.empty()) + { + if (!result.empty()) + { + result += delimiter; + } + result += string; + } + } + return result; + } + + // Join non-empty strings from values using stringify(index, value) and delimiter + template<class C, class V> + static std::string join(const C& values, std::function<std::string(size_t index, const V&)> stringify, T delimiter = ',') + { + std::string result; + for (size_t i = 0; i < values.size(); ++i) + { + std::string string = stringify(i, values[i]); + if (!string.empty()) + { + if (!result.empty()) + { + result += delimiter; + } + result += string; + } + } + return result; + } + #ifdef _DEBUG LL_COMMON_API static void testHarness(); #endif diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index f3876ef695..da63003f39 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -120,7 +120,6 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p) mButton = LLUICtrlFactory::create<LLButton>(button_params); - if (mAllowTextEntry) { //redo to compensate for button hack that leaves space for a character @@ -142,16 +141,12 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p) // Grab the mouse-up event and make sure the button state is correct mList->setMouseUpCallback(boost::bind(&LLComboBox::onListMouseUp, this)); - for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items.begin(); - it != p.items.end(); - ++it) + for (LLComboBox::ItemParams item_params : p.items) { - LLScrollListItem::Params item_params = *it; - if (it->label.isProvided()) + if (item_params.label.isProvided()) { - item_params.columns.add().value(it->label()); + item_params.columns.add().value(item_params.label()); } - mList->addRow(item_params); } @@ -235,7 +230,7 @@ void LLComboBox::onCommit() bool LLComboBox::isDirty() const { bool grubby = false; - if ( mList ) + if (mList) { grubby = mList->isDirty(); } @@ -243,9 +238,9 @@ bool LLComboBox::isDirty() const } // virtual Clear dirty state -void LLComboBox::resetDirty() +void LLComboBox::resetDirty() { - if ( mList ) + if (mList) { mList->resetDirty(); } @@ -256,6 +251,11 @@ bool LLComboBox::itemExists(const std::string& name) return mList->getItemByLabel(name); } +std::vector<LLScrollListItem*> LLComboBox::getAllData() const +{ + return mList->getAllData(); +} + // add item "name" to menu LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, bool enabled) { @@ -299,7 +299,7 @@ LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddP { LLScrollListItem* item = mList->addSimpleElement(name, pos); item->setEnabled(enabled); - item->setUserdata( userdata ); + item->setUserdata(userdata); if (!mAllowTextEntry && mLabel.empty()) { if (mControlVariable) @@ -343,7 +343,6 @@ void LLComboBox::sortByName(bool ascending) mList->sortOnce(0, ascending); } - // Choose an item with a given name in the menu. // Returns true if the item was found. bool LLComboBox::setSimple(const LLStringExplicit& name) @@ -369,11 +368,9 @@ void LLComboBox::setValue(const LLSD& value) return; } - bool found = mList->selectByValue(value); - if (found) + if (mList->selectByValue(value)) { - LLScrollListItem* item = mList->getFirstSelected(); - if (item) + if (mList->getFirstSelected()) { updateLabel(); } @@ -404,7 +401,8 @@ const std::string LLComboBox::getSelectedItemLabel(S32 column) const // virtual LLSD LLComboBox::getValue() const { - if (LLScrollListItem* item = mList->getFirstSelected()) + LLScrollListItem* item = mList->getFirstSelected(); + if(item) { return item->getValue(); } @@ -590,8 +588,7 @@ bool LLComboBox::selectPrevItem() void LLComboBox::setEnabledByValue(const LLSD& value, bool enabled) { - LLScrollListItem *found = mList->getItem(value); - if (found) + if (LLScrollListItem* found = mList->getItem(value)) { found->setEnabled(enabled); } @@ -654,7 +651,7 @@ void LLComboBox::setLeftTextPadding(S32 pad) void* LLComboBox::getCurrentUserdata() { LLScrollListItem* item = mList->getFirstSelected(); - if( item ) + if (item) { return item->getUserdata(); } @@ -760,15 +757,19 @@ void LLComboBox::hideList() if (mList->getVisible()) { // assert selection in list - if(mAllowNewValues) + if (mAllowNewValues) { // mLastSelectedIndex = -1 means that we entered a new value, don't select // any of existing items in this case. - if(mLastSelectedIndex >= 0) + if (mLastSelectedIndex >= 0) + { mList->selectNthItem(mLastSelectedIndex); + } } - else if(mLastSelectedIndex >= 0) + else if (mLastSelectedIndex >= 0) + { mList->selectNthItem(mLastSelectedIndex); + } mButton->setToggleState(false); mList->setVisible(false); @@ -854,7 +855,7 @@ bool LLComboBox::handleToolTip(S32 x, S32 y, MASK mask) { std::string tool_tip; - if(LLUICtrl::handleToolTip(x, y, mask)) + if (LLUICtrl::handleToolTip(x, y, mask)) { return true; } @@ -871,6 +872,7 @@ bool LLComboBox::handleToolTip(S32 x, S32 y, MASK mask) .message(tool_tip) .sticky_rect(calcScreenRect())); } + return true; } diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h index 8be3eb57e4..06687e9368 100644 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -144,6 +144,7 @@ public: bool remove( S32 index ); // remove item by index, return true if found and removed void removeall() { clearRows(); } bool itemExists(const std::string& name); + std::vector<LLScrollListItem*> getAllData() const; void sortByName(bool ascending = true); // Sort the entries in the combobox by name diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 8998d965fb..07c4b5681b 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -326,17 +326,11 @@ S32 LLScrollListCtrl::getSearchColumn() } return llclamp(mSearchColumn, 0, getNumColumns()); } -/*virtual*/ + +// virtual bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child) { - if (child->hasName("column") || child->hasName("row")) - { - return true; // skip - } - else - { - return false; - } + return child->hasName("column") || child->hasName("row"); } LLScrollListCtrl::~LLScrollListCtrl() @@ -356,7 +350,6 @@ LLScrollListCtrl::~LLScrollListCtrl() } } - bool LLScrollListCtrl::setMaxItemCount(S32 max_count) { if (max_count >= getItemCount()) @@ -379,10 +372,9 @@ S32 LLScrollListCtrl::getItemCount() const bool LLScrollListCtrl::hasSelectedItem() const { item_list::iterator iter; - for (iter = mItemList.begin(); iter < mItemList.end(); ) + for (LLScrollListItem* item : mItemList) { - LLScrollListItem* itemp = *iter; - if (itemp && itemp->getSelected()) + if (item->getSelected()) { return true; } @@ -396,7 +388,6 @@ void LLScrollListCtrl::clearRows() { std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); mItemList.clear(); - //mItemCount = 0; // Scroll the bar back up to the top. mScrollbar->setDocParams(0, 0); @@ -407,7 +398,6 @@ void LLScrollListCtrl::clearRows() mDirty = false; } - LLScrollListItem* LLScrollListCtrl::getFirstSelected() const { for (LLScrollListItem* item : mItemList) @@ -444,14 +434,12 @@ S32 LLScrollListCtrl::getNumSelected() const ++numSelected; } } - return numSelected; } S32 LLScrollListCtrl::getFirstSelectedIndex() const { S32 CurSelectedIndex = 0; - // make sure sort is up to date before returning an index updateSort(); @@ -463,7 +451,6 @@ S32 LLScrollListCtrl::getFirstSelectedIndex() const } CurSelectedIndex++; } - return -1; } @@ -477,6 +464,15 @@ LLScrollListItem* LLScrollListCtrl::getLastData() const return mItemList.empty() ? NULL : mItemList.back(); } +LLScrollListItem* LLScrollListCtrl::getNthData(size_t index) const +{ + if (mItemList.size() <= index) + { + return NULL; + } + return mItemList[index]; +} + std::vector<LLScrollListItem*> LLScrollListCtrl::getAllData() const { std::vector<LLScrollListItem*> ret; @@ -1052,7 +1048,7 @@ S32 LLScrollListCtrl::selectMultiple( uuid_vec_t ids ) { LLScrollListItem* item = *iter; uuid_vec_t::iterator iditr; - for(iditr = ids.begin(); iditr != ids.end(); ++iditr) + for (iditr = ids.begin(); iditr != ids.end(); ++iditr) { if (item->getEnabled() && (item->getUUID() == (*iditr))) { @@ -1062,7 +1058,10 @@ S32 LLScrollListCtrl::selectMultiple( uuid_vec_t ids ) break; } } - if(ids.end() != iditr) ids.erase(iditr); + if (ids.end() != iditr) + { + ids.erase(iditr); + } } if (mCommitOnSelectionChange) @@ -1077,11 +1076,9 @@ S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const updateSort(); S32 index = 0; - item_list::const_iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* item : mItemList) { - LLScrollListItem *itemp = *iter; - if (target_item == itemp) + if (target_item == item) { return index; } @@ -1095,11 +1092,9 @@ S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const updateSort(); S32 index = 0; - item_list::const_iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* item : mItemList) { - LLScrollListItem *itemp = *iter; - if (target_id == itemp->getUUID()) + if (target_id == item->getUUID()) { return index; } @@ -1122,10 +1117,8 @@ void LLScrollListCtrl::selectPrevItem( bool extend_selection) updateSort(); item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* cur_item : mItemList) { - LLScrollListItem* cur_item = *iter; - if (cur_item->getSelected()) { if (prev_item) @@ -1164,11 +1157,8 @@ void LLScrollListCtrl::selectNextItem( bool extend_selection) { updateSort(); - item_list::reverse_iterator iter; - for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++) + for (LLScrollListItem* cur_item : mItemList) { - LLScrollListItem* cur_item = *iter; - if (cur_item->getSelected()) { if (next_item) @@ -1197,10 +1187,8 @@ void LLScrollListCtrl::selectNextItem( bool extend_selection) void LLScrollListCtrl::deselectAllItems(bool no_commit_on_change) { - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* item : mItemList) { - LLScrollListItem* item = *iter; deselectItem(item); } @@ -2624,7 +2612,9 @@ bool LLScrollListCtrl::isRepeatedChars(const LLWString& string) const void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, S32 cell, bool select_single_item) { - if (itemp && !itemp->getSelected()) + if (!itemp) return; + + if (!itemp->getSelected() || itemp->getSelectedCell() != cell) { if (mLastSelected) { @@ -2900,11 +2890,9 @@ bool LLScrollListCtrl::canCut() const void LLScrollListCtrl::selectAll() { // Deselects all other items - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + for (LLScrollListItem* itemp : mItemList) { - LLScrollListItem *itemp = *iter; - if( itemp->getEnabled() ) + if (itemp->getEnabled()) { selectItem(itemp, -1, false); } @@ -2944,7 +2932,8 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params, EAddPosition pos) { - if (!column_params.validateBlock()) return; + if (!column_params.validateBlock()) + return; std::string name = column_params.name; // if no column name provided, just use ordinal as name @@ -2971,7 +2960,7 @@ void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params { new_column->setWidth((S32)ll_round(new_column->mRelWidth*mItemListRect.getWidth())); } - else if(new_column->mDynamicWidth) + else if (new_column->mDynamicWidth) { mNumDynamicWidthColumns++; new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns); @@ -2979,14 +2968,12 @@ void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params S32 top = mItemListRect.mTop; S32 left = mItemListRect.mLeft; - for (column_map_t::iterator itor = mColumns.begin(); - itor != mColumns.end(); - ++itor) + for (const auto& data_pair : mColumns) { - if (itor->second->mIndex < new_column->mIndex && - itor->second->getWidth() > 0) + if (data_pair.second->mIndex < new_column->mIndex && + data_pair.second->getWidth() > 0) { - left += itor->second->getWidth() + mColumnPadding; + left += data_pair.second->getWidth() + mColumnPadding; } } @@ -3152,17 +3139,16 @@ LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& item_p, EAddPosition pos) { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - if (!item_p.validateBlock() || !new_item) return NULL; - new_item->setNumColumns(static_cast<S32>(mColumns.size())); + if (!item_p.validateBlock() || !new_item) + return nullptr; + + new_item->setNumColumns((S32)mColumns.size()); // Add any columns we don't already have S32 col_index = 0; - for(LLInitParam::ParamIterator<LLScrollListCell::Params>::const_iterator itor = item_p.columns.begin(); - itor != item_p.columns.end(); - ++itor) + for (LLScrollListCell::Params cell_p : item_p.columns) { - LLScrollListCell::Params cell_p = *itor; std::string column = cell_p.column; // empty columns strings index by ordinal @@ -3224,8 +3210,7 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS new_item->setNumColumns(static_cast<S32>(mColumns.size())); } - LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value)); - if (cell) + if (LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value))) { LLScrollListColumn* columnp = mColumns.begin()->second; @@ -3240,15 +3225,13 @@ LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLS } // add dummy cells for missing columns - for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it) + for (const auto& column_it : mColumns) { - S32 column_idx = column_it->second->mIndex; + S32 column_idx = column_it.second->mIndex; if (new_item->getColumn(column_idx) == NULL) { - LLScrollListColumn* column_ptr = column_it->second; LLScrollListCell::Params cell_p; - cell_p.width = column_ptr->getWidth(); - + cell_p.width = column_it.second->getWidth(); new_item->setColumn(column_idx, new LLScrollListSpacer(cell_p)); } } diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index 1f04100306..bfae08ab5b 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -290,6 +290,7 @@ public: // iterate over all items LLScrollListItem* getFirstData() const; LLScrollListItem* getLastData() const; + LLScrollListItem* getNthData(size_t index) const; std::vector<LLScrollListItem*> getAllData() const; LLScrollListItem* getItem(const LLSD& sd) const; diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp index a1ab2dd52d..23849aca66 100644 --- a/indra/llwindow/llgamecontrol.cpp +++ b/indra/llwindow/llgamecontrol.cpp @@ -37,80 +37,202 @@ #include "indra_constants.h" #include "llfile.h" #include "llgamecontroltranslator.h" +#include "llsd.h" -constexpr size_t NUM_AXES = 6; - -// util for dumping SDL_GameController info -std::ostream& operator<<(std::ostream& out, SDL_GameController* c) +namespace std { - if (!c) + string to_string(const char* text) + { + return text ? string(text) : LLStringUtil::null; + } + + string to_string(const SDL_JoystickGUID& guid) + { + char buffer[33] = { 0 }; + SDL_JoystickGetGUIDString(guid, buffer, sizeof(guid)); + return buffer; + } + + string to_string(SDL_JoystickType type) + { + switch (type) + { + case SDL_JOYSTICK_TYPE_GAMECONTROLLER: + return "GAMECONTROLLER"; + case SDL_JOYSTICK_TYPE_WHEEL: + return "WHEEL"; + case SDL_JOYSTICK_TYPE_ARCADE_STICK: + return "ARCADE_STICK"; + case SDL_JOYSTICK_TYPE_FLIGHT_STICK: + return "FLIGHT_STICK"; + case SDL_JOYSTICK_TYPE_DANCE_PAD: + return "DANCE_PAD"; + case SDL_JOYSTICK_TYPE_GUITAR: + return "GUITAR"; + case SDL_JOYSTICK_TYPE_DRUM_KIT: + return "DRUM_KIT"; + case SDL_JOYSTICK_TYPE_ARCADE_PAD: + return "ARCADE_PAD"; + case SDL_JOYSTICK_TYPE_THROTTLE: + return "THROTTLE"; + default:; + } + return "UNKNOWN"; + } + + string to_string(SDL_GameControllerType type) { - return out << "nullptr"; + switch (type) + { + case SDL_CONTROLLER_TYPE_XBOX360: + return "XBOX360"; + case SDL_CONTROLLER_TYPE_XBOXONE: + return "XBOXONE"; + case SDL_CONTROLLER_TYPE_PS3: + return "PS3"; + case SDL_CONTROLLER_TYPE_PS4: + return "PS4"; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: + return "NINTENDO_SWITCH_PRO"; + case SDL_CONTROLLER_TYPE_VIRTUAL: + return "VIRTUAL"; + case SDL_CONTROLLER_TYPE_PS5: + return "PS5"; + case SDL_CONTROLLER_TYPE_AMAZON_LUNA: + return "AMAZON_LUNA"; + case SDL_CONTROLLER_TYPE_GOOGLE_STADIA: + return "GOOGLE_STADIA"; + case SDL_CONTROLLER_TYPE_NVIDIA_SHIELD: + return "NVIDIA_SHIELD"; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + return "NINTENDO_SWITCH_JOYCON_LEFT"; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + return "NINTENDO_SWITCH_JOYCON_RIGHT"; + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return "NINTENDO_SWITCH_JOYCON_PAIR"; + default:; + } + return "UNKNOWN"; } - out << "{"; - out << " name='" << SDL_GameControllerName(c) << "'"; - out << " type='" << SDL_GameControllerGetType(c) << "'"; - out << " vendor='" << SDL_GameControllerGetVendor(c) << "'"; - out << " product='" << SDL_GameControllerGetProduct(c) << "'"; - out << " version='" << SDL_GameControllerGetProductVersion(c) << "'"; - //CRASH! out << " serial='" << SDL_GameControllerGetSerial(c) << "'"; - out << " }"; - return out; } -// util for dumping SDL_Joystick info -std::ostream& operator<<(std::ostream& out, SDL_Joystick* j) +// Util for dumping SDL_JoystickGUID info +std::ostream& operator<<(std::ostream& out, SDL_JoystickGUID& guid) +{ + return out << std::to_string(guid); +} + +// Util for dumping SDL_JoystickType type name +std::ostream& operator<<(std::ostream& out, SDL_JoystickType type) +{ + return out << std::to_string(type); +} + +// Util for dumping SDL_GameControllerType type name +std::ostream& operator<<(std::ostream& out, SDL_GameControllerType type) +{ + return out << std::to_string(type); +} + +namespace std { - if (!j) + string to_string(SDL_Joystick* joystick) { - return out << "nullptr"; + if (!joystick) + { + return "nullptr"; + } + + std::stringstream ss; + + ss << "{id:" << SDL_JoystickInstanceID(joystick); + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + ss << ",guid:'" << guid << "'"; + ss << ",type:'" << SDL_JoystickGetType(joystick) << "'"; + ss << ",name:'" << std::to_string(SDL_JoystickName(joystick)) << "'"; + ss << ",vendor:" << SDL_JoystickGetVendor(joystick); + ss << ",product:" << SDL_JoystickGetProduct(joystick); + if (U16 version = SDL_JoystickGetProductVersion(joystick)) + { + ss << ",version:" << version; + } + if (U16 firmware = SDL_JoystickGetFirmwareVersion(joystick)) + { + ss << ",firmware:" << firmware; + } + if (const char* serial = SDL_JoystickGetSerial(joystick)) + { + ss << ",serial:'" << serial << "'"; + } + ss << ",num_axes:" << SDL_JoystickNumAxes(joystick); + ss << ",num_balls:" << SDL_JoystickNumBalls(joystick); + ss << ",num_hats:" << SDL_JoystickNumHats(joystick); + ss << ",num_buttons:" << SDL_JoystickNumHats(joystick); + ss << "}"; + + return ss.str(); } - out << "{"; - out << " p=0x" << (void*)(j); - out << " name='" << SDL_JoystickName(j) << "'"; - out << " type='" << SDL_JoystickGetType(j) << "'"; - out << " instance='" << SDL_JoystickInstanceID(j) << "'"; - out << " product='" << SDL_JoystickGetProduct(j) << "'"; - out << " version='" << SDL_JoystickGetProductVersion(j) << "'"; - out << " num_axes=" << SDL_JoystickNumAxes(j); - out << " num_balls=" << SDL_JoystickNumBalls(j); - out << " num_hats=" << SDL_JoystickNumHats(j); - out << " num_buttons=" << SDL_JoystickNumHats(j); - out << " }"; - return out; + + string to_string(SDL_GameController* controller) + { + if (!controller) + { + return "nullptr"; + } + + stringstream ss; + + ss << "{type:'" << SDL_GameControllerGetType(controller) << "'"; + ss << ",name:'" << std::to_string(SDL_GameControllerName(controller)) << "'"; + ss << ",vendor:" << SDL_GameControllerGetVendor(controller); + ss << ",product:" << SDL_GameControllerGetProduct(controller); + if (U16 version = SDL_GameControllerGetProductVersion(controller)) + { + ss << ",version:" << version; + } + if (U16 firmware = SDL_GameControllerGetFirmwareVersion(controller)) + { + ss << ",firmware:" << firmware; + } + if (const char* serial = SDL_GameControllerGetSerial(controller)) + { + ss << ",serial:'" << serial << "'"; + } + ss << "}"; + + return ss.str(); + } +} + +// Util for dumping SDL_Joystick info +std::ostream& operator<<(std::ostream& out, SDL_Joystick* joystick) +{ + return out << std::to_string(joystick); +} + +// Util for dumping SDL_GameController info +std::ostream& operator<<(std::ostream& out, SDL_GameController* controller) +{ + return out << std::to_string(controller); } std::string LLGameControl::InputChannel::getLocalName() const { // HACK: we hard-code English channel names, but // they should be loaded from localized XML config files. - std::string name = " "; - if (mType == LLGameControl::InputChannel::TYPE_AXIS) + + if ((mType == LLGameControl::InputChannel::TYPE_AXIS) && (mIndex < NUM_AXES)) { - if (mIndex < (U8)(NUM_AXES)) - { - name = "AXIS_"; - name.append(std::to_string((S32)(mIndex))); - if (mSign < 0) - { - name.append("-"); - } - else if (mSign > 0) - { - name.append("+"); - } - } + return "AXIS_" + std::to_string((U32)mIndex) + + (mSign < 0 ? "-" : mSign > 0 ? "+" : ""); } - else if (mType == LLGameControl::InputChannel::TYPE_BUTTON) + + if ((mType == LLGameControl::InputChannel::TYPE_BUTTON) && (mIndex < NUM_BUTTONS)) { - constexpr U8 NUM_BUTTONS = 32; - if (mIndex < NUM_BUTTONS) - { - name = "BUTTON_"; - name.append(std::to_string((S32)(mIndex))); - } + return "BUTTON_" + std::to_string((U32)mIndex); } - return name; + + return "NONE"; } std::string LLGameControl::InputChannel::getRemoteName() const @@ -121,7 +243,7 @@ std::string LLGameControl::InputChannel::getRemoteName() const // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc if (mType == LLGameControl::InputChannel::TYPE_AXIS) { - switch(mIndex) + switch (mIndex) { case 0: name = "GAME_CONTROL_AXIS_LEFTX"; @@ -227,7 +349,15 @@ public: using ActionToChannelMap = std::map< std::string, LLGameControl::InputChannel >; LLGameControllerManager(); - void addController(SDL_JoystickID id, SDL_GameController* controller); + static void getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& agent_channels, + std::vector<LLGameControl::InputChannel>& flycam_channels); + void getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings); + void initializeMappingsByDefault(); + void resetDeviceOptionsToDefaults(); + void loadDeviceOptionsFromSettings(); + void saveDeviceOptionsToSettings() const; + + void addController(SDL_JoystickID id, const std::string& guid, const std::string& name); void removeController(SDL_JoystickID id); void onAxis(SDL_JoystickID id, U8 axis, S16 value); @@ -236,39 +366,56 @@ public: void clearAllStates(); void accumulateInternalState(); - void computeFinalState(LLGameControl::State& state); + void computeFinalState(); - LLGameControl::InputChannel getChannelByActionName(const std::string& action_name) const; - LLGameControl::InputChannel getFlycamChannelByActionName(const std::string& action_name) const; + LLGameControl::ActionNameType getActionNameType(const std::string& action) const; + LLGameControl::InputChannel getChannelByAction(const std::string& action) const; + LLGameControl::InputChannel getFlycamChannelByAction(const std::string& action) const; bool updateActionMap(const std::string& name, LLGameControl::InputChannel channel); U32 computeInternalActionFlags(); void getFlycamInputs(std::vector<F32>& inputs_out); void setExternalInput(U32 action_flags, U32 buttons); + U32 getMappedFlags() const { return mActionTranslator.getMappedFlags(); } + void clear(); + std::string getAnalogMappings() const; + std::string getBinaryMappings() const; + std::string getFlycamMappings() const; + + void setAnalogMappings(const std::string& mappings); + void setBinaryMappings(const std::string& mappings); + void setFlycamMappings(const std::string& mappings); + private: - bool updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel); + void updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel); - std::list<LLGameControl::State> mStates; // one state per device - using state_it = std::list<LLGameControl::State>::iterator; - state_it findState(SDL_JoystickID id) + std::list<LLGameControl::Device> mDevices; // all connected devices + using device_it = std::list<LLGameControl::Device>::iterator; + device_it findDevice(SDL_JoystickID id) { - return std::find_if(mStates.begin(), mStates.end(), - [id](LLGameControl::State& state) + return std::find_if(mDevices.begin(), mDevices.end(), + [id](LLGameControl::Device& device) { - return state.getJoystickID() == id; + return device.getJoystickID() == id; }); } LLGameControl::State mExternalState; LLGameControlTranslator mActionTranslator; + std::map<std::string, LLGameControl::ActionNameType> mActions; + std::vector <std::string> mAnalogActions; + std::vector <std::string> mBinaryActions; + std::vector <std::string> mFlycamActions; std::vector<LLGameControl::InputChannel> mFlycamChannels; std::vector<S32> mAxesAccumulator; U32 mButtonAccumulator { 0 }; U32 mLastActiveFlags { 0 }; U32 mLastFlycamActionFlags { 0 }; + + friend class LLGameControl; }; // local globals @@ -301,8 +448,52 @@ namespace bool g_translateAgentActions = false; LLGameControl::AgentControlMode g_agentControlMode = LLGameControl::CONTROL_MODE_AVATAR; - constexpr U8 MAX_AXIS = 5; - constexpr U8 MAX_BUTTON = 31; + std::map<std::string, std::string> g_deviceOptions; + + std::function<bool(const std::string&)> s_loadBoolean; + std::function<void(const std::string&, bool)> s_saveBoolean; + std::function<std::string(const std::string&)> s_loadString; + std::function<void(const std::string&, const std::string&)> s_saveString; + std::function<LLSD(const std::string&)> s_loadObject; + std::function<void(const std::string&, LLSD&)> s_saveObject; + + std::string SETTING_SENDTOSERVER("GameControlToServer"); + std::string SETTING_CONTROLAGENT("GameControlToAgent"); + std::string SETTING_TRANSLATEACTIONS("AgentToGameControl"); + std::string SETTING_AGENTCONTROLMODE("AgentControlMode"); + std::string SETTING_ANALOGMAPPINGS("AnalogChannelMappings"); + std::string SETTING_BINARYMAPPINGS("BinaryChannelMappings"); + std::string SETTING_FLYCAMMAPPINGS("FlycamChannelMappings"); + std::string SETTING_KNOWNCONTROLLERS("KnownGameControllers"); + + std::string ENUM_AGENTCONTROLMODE_FLYCAM("flycam"); + std::string ENUM_AGENTCONTROLMODE_NONE("none"); + + LLGameControl::AgentControlMode convertStringToAgentControlMode(const std::string& mode) + { + if (mode == ENUM_AGENTCONTROLMODE_NONE) + return LLGameControl::CONTROL_MODE_NONE; + if (mode == ENUM_AGENTCONTROLMODE_FLYCAM) + return LLGameControl::CONTROL_MODE_FLYCAM; + // All values except NONE and FLYCAM are treated as default (AVATAR) + return LLGameControl::CONTROL_MODE_AVATAR; + } + + const std::string& convertAgentControlModeToString(LLGameControl::AgentControlMode mode) + { + if (mode == LLGameControl::CONTROL_MODE_NONE) + return ENUM_AGENTCONTROLMODE_NONE; + if (mode == LLGameControl::CONTROL_MODE_FLYCAM) + return ENUM_AGENTCONTROLMODE_FLYCAM; + // All values except NONE and FLYCAM are treated as default (AVATAR) + return LLStringUtil::null; + } + + const std::string& getDeviceOptionsString(const std::string& guid) + { + const auto& it = g_deviceOptions.find(guid); + return it == g_deviceOptions.end() ? LLStringUtil::null : it->second; + } } LLGameControl::~LLGameControl() @@ -310,18 +501,13 @@ LLGameControl::~LLGameControl() terminate(); } -LLGameControl::State::State() : mButtons(0) +LLGameControl::State::State() +: mButtons(0) { mAxes.resize(NUM_AXES, 0); mPrevAxes.resize(NUM_AXES, 0); } -void LLGameControl::State::setDevice(int joystickID, void* controller) -{ - mJoystickID = joystickID; - mController = controller; -} - void LLGameControl::State::clear() { std::fill(mAxes.begin(), mAxes.end(), 0); @@ -335,7 +521,7 @@ void LLGameControl::State::clear() bool LLGameControl::State::onButton(U8 button, bool pressed) { U32 old_buttons = mButtons; - if (button <= MAX_BUTTON) + if (button < NUM_BUTTONS) { if (pressed) { @@ -346,152 +532,525 @@ bool LLGameControl::State::onButton(U8 button, bool pressed) mButtons &= ~(0x01 << button); } } - bool changed = (old_buttons != mButtons); - return changed; + return mButtons != old_buttons; +} + +LLGameControl::Device::Device(int joystickID, const std::string& guid, const std::string& name) +: mJoystickID(joystickID) +, mGUID(guid) +, mName(name) +{ +} + +LLGameControl::Options::Options() +{ + mAxisOptions.resize(NUM_AXES); + mAxisMap.resize(NUM_AXES); + mButtonMap.resize(NUM_BUTTONS); + + resetToDefaults(); +} + +void LLGameControl::Options::resetToDefaults() +{ + for (size_t i = 0; i < NUM_AXES; ++i) + { + mAxisOptions[i].resetToDefaults(); + mAxisMap[i] = (U8)i; + } + + for (size_t i = 0; i < NUM_BUTTONS; ++i) + { + mButtonMap[i] = (U8)i; + } +} + +U8 LLGameControl::Options::mapAxis(U8 axis) const +{ + if (axis >= NUM_AXES) + { + LL_WARNS("SDL2") << "Invalid input axis: " << axis << LL_ENDL; + return axis; + } + return mAxisMap[axis]; +} + +U8 LLGameControl::Options::mapButton(U8 button) const +{ + if (button >= NUM_BUTTONS) + { + LL_WARNS("SDL2") << "Invalid input button: " << button << LL_ENDL; + return button; + } + return mButtonMap[button]; +} + +S16 LLGameControl::Options::fixAxisValue(U8 axis, S16 value) const +{ + if (axis >= NUM_AXES) + { + LL_WARNS("SDL2") << "Invalid input axis: " << axis << LL_ENDL; + } + else + { + const AxisOptions& options = mAxisOptions[axis]; + S32 new_value = (S32)value + (S32)options.mOffset; + value = (S16)std::clamp(new_value , -32768, 32767); + if ((value > 0 && value < (S16)options.mDeadZone) || + (value < 0 && value > -(S16)options.mDeadZone)) + { + value = 0; + } + else if (options.mInvert) + { + value = -value; + } + } + return value; +} + +std::string LLGameControl::Options::AxisOptions::saveToString() const +{ + std::list<std::string> options; + + if (mInvert) + { + options.push_back("invert:1"); + } + if (mDeadZone) + { + options.push_back(llformat("dead_zone:%u", mDeadZone)); + } + if (mOffset) + { + options.push_back(llformat("offset:%d", mOffset)); + } + + std::string result = LLStringUtil::join(options); + + return result.empty() ? result : "{" + result + "}"; +} + +// Parse string "{key:value,key:{key:value,key:value}}" and fill the map +static bool parse(std::map<std::string, std::string>& result, std::string source) +{ + result.clear(); + + LLStringUtil::trim(source); + if (source.empty()) + return true; + + if (source.front() != '{' || source.back() != '}') + return false; + + source = source.substr(1, source.size() - 2); + + LLStringUtil::trim(source); + if (source.empty()) + return true; + + // Split the string "key:value" and add the pair to the map + auto split = [&](const std::string& pair) -> bool + { + size_t pos = pair.find(':'); + if (!pos || pos == std::string::npos) + return false; + std::string key = pair.substr(0, pos); + std::string value = pair.substr(pos + 1); + LLStringUtil::trim(key); + LLStringUtil::trim(value); + if (key.empty() || value.empty()) + return false; + result[key] = value; + return true; + }; + + U32 depth = 0; + size_t offset = 0; + while (true) + { + size_t pos = source.find_first_of(depth ? "{}" : ",{}", offset); + if (pos == std::string::npos) + { + return !depth && split(source); + } + if (source[pos] == ',') + { + if (!split(source.substr(0, pos))) + return false; + source = source.substr(pos + 1); + offset = 0; + } + else if (source[pos] == '{') + { + depth++; + offset = pos + 1; + } + else if (depth) // Assume '}' here + { + depth--; + offset = pos + 1; + } + else + { + return false; // Extra '}' found + } + } + + return true; +} + +void LLGameControl::Options::AxisOptions::loadFromString(std::string options) +{ + resetToDefaults(); + + if (options.empty()) + return; + + std::map<std::string, std::string> pairs; + if (!parse(pairs, options)) + { + LL_WARNS("SDL2") << "Invalid axis options: '" << options << "'" << LL_ENDL; + } + + std::string invert = pairs["invert"]; + if (!invert.empty()) + { + if (invert != "1") + { + LL_WARNS("SDL2") << "Invalid invert value: '" << invert << "'" << LL_ENDL; + } + else + { + mInvert = true; + } + } + + std::string dead_zone = pairs["dead_zone"]; + if (!dead_zone.empty()) + { + size_t number = std::stoull(dead_zone); + if (number > MAX_AXIS_DEAD_ZONE || std::to_string(number) != dead_zone) + { + LL_WARNS("SDL2") << "Invalid dead_zone value: '" << dead_zone << "'" << LL_ENDL; + } + else + { + mDeadZone = (U16)number; + } + } + + std::string offset = pairs["offset"]; + if (!offset.empty()) + { + S32 number = std::stoi(offset); + if (abs(number) > MAX_AXIS_OFFSET || std::to_string(number) != offset) + { + LL_WARNS("SDL2") << "Invalid offset value: '" << offset << "'" << LL_ENDL; + } + else + { + mOffset = (S16)number; + } + } +} + +std::string LLGameControl::Options::saveToString(const std::string& name, bool force_empty) const +{ + return stringifyDeviceOptions(name, mAxisOptions, mAxisMap, mButtonMap, force_empty); +} + +bool LLGameControl::Options::loadFromString(std::string& name, std::string options) +{ + return LLGameControl::parseDeviceOptions(options, name, mAxisOptions, mAxisMap, mButtonMap); +} + +bool LLGameControl::Options::loadFromString(std::string options) +{ + std::string dummy_name; + return LLGameControl::parseDeviceOptions(options, dummy_name, mAxisOptions, mAxisMap, mButtonMap); } LLGameControllerManager::LLGameControllerManager() { - mAxesAccumulator.resize(NUM_AXES, 0); + mAxesAccumulator.resize(LLGameControl::NUM_AXES, 0); + + mAnalogActions = { "push", "slide", "jump", "turn", "look" }; + mBinaryActions = { "toggle_run", "toggle_fly", "toggle_flycam", "stop" }; + mFlycamActions = { "advance", "pan", "rise", "pitch", "yaw", "zoom" }; + + // Collect all known action names with their types in one container + for (const std::string& name : mAnalogActions) + { + mActions[name] = LLGameControl::ACTION_NAME_ANALOG; + mActions[name + "+"] = LLGameControl::ACTION_NAME_ANALOG_POS; + mActions[name + "-"] = LLGameControl::ACTION_NAME_ANALOG_NEG; + } + for (const std::string& name : mBinaryActions) + { + mActions[name] = LLGameControl::ACTION_NAME_BINARY; + } + for (const std::string& name : mFlycamActions) + { + mActions[name] = LLGameControl::ACTION_NAME_FLYCAM; + } // Here we build an invariant map between the named agent actions - // and control bit sent to the server. This map will be used, + // and control bit sent to the server. This map will be used, // in combination with the action->InputChannel map below, // to maintain an inverse map from control bit masks to GameControl data. - LLGameControlTranslator::ActionToMaskMap actions; - actions["push+"] = AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT; - actions["push-"] = AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT; - actions["slide+"] = AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT; - actions["slide-"] = AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT; - actions["jump+"] = AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP; - actions["jump-"] = AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP; - actions["turn+"] = AGENT_CONTROL_YAW_POS; - actions["turn-"] = AGENT_CONTROL_YAW_NEG; - actions["look+"] = AGENT_CONTROL_PITCH_POS; - actions["look-"] = AGENT_CONTROL_PITCH_NEG; - actions["stop"] = AGENT_CONTROL_STOP; + LLGameControlTranslator::ActionToMaskMap actionMasks = + { + // Analog actions (pairs) + { "push+", AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT }, + { "push-", AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT }, + { "slide+", AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT }, + { "slide-", AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT }, + { "jump+", AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP }, + { "jump-", AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP }, + { "turn+", AGENT_CONTROL_YAW_POS }, + { "turn-", AGENT_CONTROL_YAW_NEG }, + { "look+", AGENT_CONTROL_PITCH_POS }, + { "look-", AGENT_CONTROL_PITCH_NEG }, + // Button actions + { "stop", AGENT_CONTROL_STOP }, // These are HACKs. We borrow some AGENT_CONTROL bits for "unrelated" features. // Not a problem because these bits are only used internally. - actions["toggle_run"] = AGENT_CONTROL_NUDGE_AT_POS; // HACK - actions["toggle_fly"] = AGENT_CONTROL_FLY; // HACK - actions["toggle_flycam"] = AGENT_CONTROL_NUDGE_AT_NEG; // HACK - mActionTranslator.setAvailableActions(actions); + { "toggle_run", AGENT_CONTROL_NUDGE_AT_POS }, // HACK + { "toggle_fly", AGENT_CONTROL_FLY }, // HACK + { "toggle_flycam", AGENT_CONTROL_NUDGE_AT_NEG }, // HACK + }; + mActionTranslator.setAvailableActionMasks(actionMasks); + + initializeMappingsByDefault(); +} +// static +void LLGameControllerManager::getDefaultMappings( + std::vector<std::pair<std::string, LLGameControl::InputChannel>>& agent_channels, + std::vector<LLGameControl::InputChannel>& flycam_channels) +{ // Here we build a list of pairs between named agent actions and // GameControl channels. Note: we only supply the non-signed names // (e.g. "push" instead of "push+" and "push-") because mActionTranslator // automatially expands action names as necessary. using type = LLGameControl::InputChannel::Type; - std::vector< std::pair< std::string, LLGameControl::InputChannel> > agent_defaults = - { - { "push", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTY), 1 } }, - { "slide", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTX), 1 } }, - { "jump", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERLEFT), 1 } }, - { "turn", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTX), 1 } }, - { "look", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTY), 1 } }, - { "toggle_run", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_LEFTSHOULDER) } }, - { "toggle_fly", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_DPAD_UP) } }, - { "toggle_flycam", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_RIGHTSHOULDER) } }, - { "stop", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_LEFTSTICK) } } + agent_channels = + { + // Analog actions (associated by common name - without '+' or '-') + { "push", { type::TYPE_AXIS, LLGameControl::AXIS_LEFTY, 1 } }, + { "slide", { type::TYPE_AXIS, LLGameControl::AXIS_LEFTX, 1 } }, + { "jump", { type::TYPE_AXIS, LLGameControl::AXIS_TRIGGERLEFT, 1 } }, + { "turn", { type::TYPE_AXIS, LLGameControl::AXIS_RIGHTX, 1 } }, + { "look", { type::TYPE_AXIS, LLGameControl::AXIS_RIGHTY, 1 } }, + // Button actions (associated by name) + { "toggle_run", { type::TYPE_BUTTON, LLGameControl::BUTTON_LEFTSHOULDER } }, + { "toggle_fly", { type::TYPE_BUTTON, LLGameControl::BUTTON_DPAD_UP } }, + { "toggle_flycam", { type::TYPE_BUTTON, LLGameControl::BUTTON_RIGHTSHOULDER } }, + { "stop", { type::TYPE_BUTTON, LLGameControl::BUTTON_LEFTSTICK } } }; - mActionTranslator.setMappings(agent_defaults); // Flycam actions don't need bitwise translation, so we maintain the map // of channels here directly rather than using an LLGameControlTranslator. - mFlycamChannels = { - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTY), 1 }, // advance - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTX), 1 }, // pan - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERRIGHT), 1 }, // rise - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTY), -1 }, // pitch - { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTX), 1 }, // yaw - { type::TYPE_NONE, 0 } // zoom + flycam_channels = + { + // Flycam actions (associated just by an order index) + { type::TYPE_AXIS, LLGameControl::AXIS_LEFTY, 1 }, // advance + { type::TYPE_AXIS, LLGameControl::AXIS_LEFTX, 1 }, // pan + { type::TYPE_AXIS, LLGameControl::AXIS_TRIGGERRIGHT, 1 }, // rise + { type::TYPE_AXIS, LLGameControl::AXIS_RIGHTY, -1 }, // pitch + { type::TYPE_AXIS, LLGameControl::AXIS_RIGHTX, 1 }, // yaw + { type::TYPE_NONE, 0 } // zoom }; } -void LLGameControllerManager::addController(SDL_JoystickID id, SDL_GameController* controller) +void LLGameControllerManager::getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings) { - LL_INFOS("GameController") << "joystick id: " << id << ", controller: " << controller << LL_ENDL; + // Join two different data structures into the one + std::vector<LLGameControl::InputChannel> flycam_channels; + getDefaultMappings(mappings, flycam_channels); + for (size_t i = 0; i < flycam_channels.size(); ++i) + { + mappings.emplace_back(mFlycamActions[i], flycam_channels[i]); + } +} + +void LLGameControllerManager::initializeMappingsByDefault() +{ + std::vector<std::pair<std::string, LLGameControl::InputChannel>> agent_channels; + getDefaultMappings(agent_channels, mFlycamChannels); + mActionTranslator.setMappings(agent_channels); +} + +void LLGameControllerManager::resetDeviceOptionsToDefaults() +{ + for (LLGameControl::Device& device : mDevices) + { + device.resetOptionsToDefaults(); + } +} + +void LLGameControllerManager::loadDeviceOptionsFromSettings() +{ + for (LLGameControl::Device& device : mDevices) + { + device.loadOptionsFromString(getDeviceOptionsString(device.getGUID())); + } +} + +void LLGameControllerManager::saveDeviceOptionsToSettings() const +{ + for (const LLGameControl::Device& device : mDevices) + { + std::string options = device.saveOptionsToString(); + if (options.empty()) + { + g_deviceOptions.erase(device.getGUID()); + } + else + { + g_deviceOptions[device.getGUID()] = options; + } + } +} +void LLGameControllerManager::addController(SDL_JoystickID id, const std::string& guid, const std::string& name) +{ llassert(id >= 0); - llassert(controller); - if (findState(id) != mStates.end()) + for (const LLGameControl::Device& device : mDevices) { - LL_WARNS("GameController") << "device already added" << LL_ENDL; - return; + if (device.getJoystickID() == id) + { + LL_WARNS("SDL2") << "device with id=" << id << " was already added" + << ", guid: '" << device.getGUID() << "'" + << ", name: '" << device.getName() << "'" + << LL_ENDL; + return; + } } - mStates.emplace_back().setDevice(id, controller); - LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec - << " controller=" << controller - << LL_ENDL; + mDevices.emplace_back(id, guid, name).loadOptionsFromString(getDeviceOptionsString(guid)); } void LLGameControllerManager::removeController(SDL_JoystickID id) { - LL_INFOS("GameController") << "joystick id: " << id << LL_ENDL; + LL_INFOS("SDL2") << "joystick id: " << id << LL_ENDL; - mStates.remove_if([id](LLGameControl::State& state) + mDevices.remove_if([id](LLGameControl::Device& device) { - return state.getJoystickID() == id; + return device.getJoystickID() == id; }); } void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 value) { - if (axis > MAX_AXIS) + device_it it = findDevice(id); + if (it == mDevices.end()) { + LL_WARNS("SDL2") << "Unknown device: joystick=0x" << std::hex << id << std::dec + << " axis=" << (S32)axis + << " value=" << (S32)value << LL_ENDL; return; } - state_it it = findState(id); - if (it != mStates.end()) + // Map axis using device-specific settings + // or leave the value unchanged + U8 mapped_axis = it->mOptions.mapAxis(axis); + if (mapped_axis != axis) { - // Note: the RAW analog joysticks provide NEGATIVE X,Y values for LEFT,FORWARD - // whereas those directions are actually POSITIVE in SL's local right-handed - // reference frame. Therefore we implicitly negate those axes here where - // they are extracted from SDL, before being used anywhere. - if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT) - { - // Note: S16 value is in range [-32768, 32767] which means - // the negative range has an extra possible value. We need - // to add (or subtract) one during negation. - if (value < 0) - { - value = - (value + 1); - } - else if (value > 0) - { - value = (-value) - 1; - } - } + LL_DEBUGS("SDL2") << "Axis mapped: joystick=0x" << std::hex << id << std::dec + << " input axis i=" << (S32)axis + << " mapped axis i=" << (S32)mapped_axis << LL_ENDL; + axis = mapped_axis; + } - LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + if (axis >= LLGameControl::NUM_AXES) + { + LL_WARNS("SDL2") << "Unknown axis: joystick=0x" << std::hex << id << std::dec << " axis=" << (S32)(axis) << " value=" << (S32)(value) << LL_ENDL; - it->mAxes[axis] = value; + return; } + + // Fix value using device-specific settings + // or leave the value unchanged + S16 fixed_value = it->mOptions.fixAxisValue(axis, value); + if (fixed_value != value) + { + LL_DEBUGS("SDL2") << "Value fixed: joystick=0x" << std::hex << id << std::dec + << " axis i=" << (S32)axis + << " input value=" << (S32)value + << " fixed value=" << (S32)fixed_value << LL_ENDL; + value = fixed_value; + } + + // Note: the RAW analog joysticks provide NEGATIVE X,Y values for LEFT,FORWARD + // whereas those directions are actually POSITIVE in SL's local right-handed + // reference frame. Therefore we implicitly negate those axes here where + // they are extracted from SDL, before being used anywhere. + if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT) + { + // Note: S16 value is in range [-32768, 32767] which means + // the negative range has an extra possible value. We need + // to add (or subtract) one during negation. + if (value < 0) + { + value = - (value + 1); + } + else if (value > 0) + { + value = (-value) - 1; + } + } + + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + << " axis=" << (S32)(axis) + << " value=" << (S32)(value) << LL_ENDL; + it->mState.mAxes[axis] = value; } void LLGameControllerManager::onButton(SDL_JoystickID id, U8 button, bool pressed) { - state_it it = findState(id); - if (it != mStates.end()) + device_it it = findDevice(id); + if (it == mDevices.end()) { - if (it->onButton(button, pressed)) - { - LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec - << " button i=" << (S32)(button) - << " pressed=" << pressed << LL_ENDL; - } + LL_WARNS("SDL2") << "Unknown device: joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)button << LL_ENDL; + return; + } + + // Map button using device-specific settings + // or leave the value unchanged + U8 mapped_button = it->mOptions.mapButton(button); + if (mapped_button != button) + { + LL_DEBUGS("SDL2") << "Button mapped: joystick=0x" << std::hex << id << std::dec + << " input button i=" << (S32)button + << " mapped button i=" << (S32)mapped_button << LL_ENDL; + button = mapped_button; + } + + if (button >= LLGameControl::NUM_BUTTONS) + { + LL_WARNS("SDL2") << "Unknown button: joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)button << LL_ENDL; + return; + } + + if (it->mState.onButton(button, pressed)) + { + LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec + << " button i=" << (S32)button + << " pressed=" << pressed << LL_ENDL; } } void LLGameControllerManager::clearAllStates() { - for (auto& state : mStates) + for (auto& device : mDevices) { - state.clear(); + device.mState.clear(); } mExternalState.clear(); mLastActiveFlags = 0; @@ -505,36 +1064,36 @@ void LLGameControllerManager::accumulateInternalState() mButtonAccumulator = 0; // accumulate the controllers - for (const auto& state : mStates) + for (const auto& device : mDevices) { - mButtonAccumulator |= state.mButtons; - for (size_t i = 0; i < NUM_AXES; ++i) + mButtonAccumulator |= device.mState.mButtons; + for (size_t i = 0; i < LLGameControl::NUM_AXES; ++i) { // Note: we don't bother to clamp the axes yet // because at this stage we haven't yet accumulated the "inner" state. - mAxesAccumulator[i] += (S32)(state.mAxes[i]); + mAxesAccumulator[i] += (S32)device.mState.mAxes[i]; } } } -void LLGameControllerManager::computeFinalState(LLGameControl::State& final_state) +void LLGameControllerManager::computeFinalState() { // We assume accumulateInternalState() has already been called and we will // finish by accumulating "external" state (if enabled) - U32 old_buttons = final_state.mButtons; - final_state.mButtons = mButtonAccumulator; + U32 old_buttons = g_finalState.mButtons; + g_finalState.mButtons = mButtonAccumulator; if (g_translateAgentActions) { // accumulate from mExternalState - final_state.mButtons |= mExternalState.mButtons; + g_finalState.mButtons |= mExternalState.mButtons; } - if (old_buttons != final_state.mButtons) + if (old_buttons != g_finalState.mButtons) { g_nextResendPeriod = 0; // packet needs to go out ASAP } // clamp the accumulated axes - for (size_t i = 0; i < NUM_AXES; ++i) + for (size_t i = 0; i < LLGameControl::NUM_AXES; ++i) { S32 axis = mAxesAccumulator[i]; if (g_translateAgentActions) @@ -543,108 +1102,211 @@ void LLGameControllerManager::computeFinalState(LLGameControl::State& final_stat // rather than onto mAxisAccumulator[i] because the internal // accumulated value is also used to drive the Flycam, and // we don't want any external state leaking into that value. - axis += (S32)(mExternalState.mAxes[i]); + axis += (S32)mExternalState.mAxes[i]; } - axis = (S16)(std::min(std::max(axis, -32768), 32767)); + axis = (S16)std::min(std::max(axis, -32768), 32767); // check for change - if (final_state.mAxes[i] != axis) + if (g_finalState.mAxes[i] != axis) { // When axis changes we explicitly update the corresponding prevAxis // prior to storing axis. The only other place where prevAxis // is updated in updateResendPeriod() which is explicitly called after // a packet is sent. The result is: unchanged axes are included in // first resend but not later ones. - final_state.mPrevAxes[i] = final_state.mAxes[i]; - final_state.mAxes[i] = axis; + g_finalState.mPrevAxes[i] = g_finalState.mAxes[i]; + g_finalState.mAxes[i] = axis; g_nextResendPeriod = 0; // packet needs to go out ASAP } } } -LLGameControl::InputChannel LLGameControllerManager::getChannelByActionName(const std::string& name) const +LLGameControl::ActionNameType LLGameControllerManager::getActionNameType(const std::string& action) const +{ + auto it = mActions.find(action); + return it == mActions.end() ? LLGameControl::ACTION_NAME_UNKNOWN : it->second; +} + +LLGameControl::InputChannel LLGameControllerManager::getChannelByAction(const std::string& action) const { - LLGameControl::InputChannel channel = mActionTranslator.getChannelByAction(name); - if (channel.isNone()) + LLGameControl::InputChannel channel; + auto action_it = mActions.find(action); + if (action_it != mActions.end()) { - // maybe we're looking for a flycam action - channel = getFlycamChannelByActionName(name); + if (action_it->second == LLGameControl::ACTION_NAME_FLYCAM) + { + channel = getFlycamChannelByAction(action); + } + else + { + channel = mActionTranslator.getChannelByAction(action); + } } return channel; } -// helper -S32 get_flycam_index_by_name(const std::string& name) +LLGameControl::InputChannel LLGameControllerManager::getFlycamChannelByAction(const std::string& action) const { - // the Flycam action<-->channel relationship - // is implicitly stored in std::vector in a known order - S32 index = -1; - if (name.rfind("advance", 0) == 0) - { - index = 0; - } - else if (name.rfind("pan", 0) == 0) - { - index = 1; - } - else if (name.rfind("rise", 0) == 0) - { - index = 2; - } - else if (name.rfind("pitch", 0) == 0) - { - index = 3; - } - else if (name.rfind("yaw", 0) == 0) - { - index = 4; - } - else if (name.rfind("zoom", 0) == 0) + auto flycam_it = std::find(mFlycamActions.begin(), mFlycamActions.end(), action); + llassert(flycam_it != mFlycamActions.end()); + std::ptrdiff_t index = std::distance(mFlycamActions.begin(), flycam_it); + return mFlycamChannels[(std::size_t)index]; +} + +// Common implementation of getAnalogMappings(), getBinaryMappings() and getFlycamMappings() +static std::string getMappings(const std::vector<std::string>& actions, LLGameControl::InputChannel::Type type, + std::function<LLGameControl::InputChannel(const std::string& action)> getChannel) +{ + std::list<std::string> mappings; + + std::vector<std::pair<std::string, LLGameControl::InputChannel>> default_mappings; + LLGameControl::getDefaultMappings(default_mappings); + + // Walk through the all known actions of the chosen type + for (const std::string& action : actions) { - index = 5; + LLGameControl::InputChannel channel = getChannel(action); + // Only channels of the expected type should be stored + if (channel.mType == type) + { + bool mapping_differs = false; + for (const auto& pair : default_mappings) + { + if (pair.first == action) + { + mapping_differs = !channel.isEqual(pair.second); + break; + } + } + // Only mappings different from the default should be stored + if (mapping_differs) + { + mappings.push_back(action + ":" + channel.getLocalName()); + } + } } - return index; + + std::string result = LLStringUtil::join(mappings); + + return result; } -LLGameControl::InputChannel LLGameControllerManager::getFlycamChannelByActionName(const std::string& name) const +std::string LLGameControllerManager::getAnalogMappings() const { - // the Flycam channels are stored in a strict order - LLGameControl::InputChannel channel; - S32 index = get_flycam_index_by_name(name); - if (index != -1) + return getMappings(mAnalogActions, LLGameControl::InputChannel::TYPE_AXIS, + [&](const std::string& action) -> LLGameControl::InputChannel + { + return mActionTranslator.getChannelByAction(action + "+"); + }); +} + +std::string LLGameControllerManager::getBinaryMappings() const +{ + return getMappings(mBinaryActions, LLGameControl::InputChannel::TYPE_BUTTON, + [&](const std::string& action) -> LLGameControl::InputChannel + { + return mActionTranslator.getChannelByAction(action); + }); +} + +std::string LLGameControllerManager::getFlycamMappings() const +{ + return getMappings(mFlycamActions, LLGameControl::InputChannel::TYPE_AXIS, + [&](const std::string& action) -> LLGameControl::InputChannel + { + return getFlycamChannelByAction(action); + }); +} + +// Common implementation of setAnalogMappings(), setBinaryMappings() and setFlycamMappings() +static void setMappings(const std::string& mappings, + const std::vector<std::string>& actions, LLGameControl::InputChannel::Type type, + std::function<void(const std::string& action, LLGameControl::InputChannel channel)> updateMap) +{ + if (mappings.empty()) + return; + + std::map<std::string, std::string> pairs; + LLStringOps::splitString(mappings, ',', [&](const std::string& mapping) + { + std::size_t pos = mapping.find(':'); + if (pos > 0 && pos != std::string::npos) + { + pairs[mapping.substr(0, pos)] = mapping.substr(pos + 1); + } + }); + + static const LLGameControl::InputChannel channelNone; + + for (const std::string& action : actions) { - channel = mFlycamChannels[index]; + auto it = pairs.find(action); + if (it != pairs.end()) + { + LLGameControl::InputChannel channel = LLGameControl::getChannelByName(it->second); + if (channel.isNone() || channel.mType == type) + { + updateMap(action, channel); + continue; + } + } + updateMap(action, channelNone); } - return channel; } -bool LLGameControllerManager::updateActionMap(const std::string& action, LLGameControl::InputChannel channel) +void LLGameControllerManager::setAnalogMappings(const std::string& mappings) +{ + setMappings(mappings, mAnalogActions, LLGameControl::InputChannel::TYPE_AXIS, + [&](const std::string& action, LLGameControl::InputChannel channel) + { + mActionTranslator.updateMap(action, channel); + }); +} + +void LLGameControllerManager::setBinaryMappings(const std::string& mappings) +{ + setMappings(mappings, mBinaryActions, LLGameControl::InputChannel::TYPE_BUTTON, + [&](const std::string& action, LLGameControl::InputChannel channel) + { + mActionTranslator.updateMap(action, channel); + }); +} + +void LLGameControllerManager::setFlycamMappings(const std::string& mappings) { - bool success = mActionTranslator.updateMap(action, channel); - if (success) + setMappings(mappings, mFlycamActions, LLGameControl::InputChannel::TYPE_AXIS, + [&](const std::string& action, LLGameControl::InputChannel channel) + { + updateFlycamMap(action, channel); + }); +} + +bool LLGameControllerManager::updateActionMap(const std::string& action, LLGameControl::InputChannel channel) +{ + auto action_it = mActions.find(action); + if (action_it == mActions.end()) { - mLastActiveFlags = 0; + LL_WARNS("SDL2") << "unmappable action='" << action << "'" << LL_ENDL; + return false; } - else + + if (action_it->second == LLGameControl::ACTION_NAME_FLYCAM) { - // maybe we're looking for a flycam action - success = updateFlycamMap(action, channel); + updateFlycamMap(action, channel); } - if (!success) + else { - LL_WARNS("GameControl") << "unmappable action='" << action << "'" << LL_ENDL; + mActionTranslator.updateMap(action, channel); } - return success; + return true; } -bool LLGameControllerManager::updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel) +void LLGameControllerManager::updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel) { - S32 index = get_flycam_index_by_name(action); - if (index != -1) - { - mFlycamChannels[index] = channel; - return true; - } - return false; + auto flycam_it = std::find(mFlycamActions.begin(), mFlycamActions.end(), action); + llassert(flycam_it != mFlycamActions.end()); + std::ptrdiff_t index = std::distance(mFlycamActions.begin(), flycam_it); + llassert(index >= 0 && (std::size_t)index < mFlycamChannels.size()); + mFlycamChannels[(std::size_t)index] = channel; } U32 LLGameControllerManager::computeInternalActionFlags() @@ -672,13 +1334,13 @@ void LLGameControllerManager::getFlycamInputs(std::vector<F32>& inputs) for (const auto& channel: mFlycamChannels) { S16 axis; - if (channel.mIndex == (U8)(LLGameControl::AXIS_TRIGGERLEFT) - || channel.mIndex == (U8)(LLGameControl::AXIS_TRIGGERRIGHT)) + if (channel.mIndex == LLGameControl::AXIS_TRIGGERLEFT || + channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) { // TIED TRIGGER HACK: we assume the two triggers are paired together - S32 total_axis = mAxesAccumulator[(U8)(LLGameControl::AXIS_TRIGGERLEFT)] - - mAxesAccumulator[(U8)(LLGameControl::AXIS_TRIGGERRIGHT)]; - if (channel.mIndex == (U8)(LLGameControl::AXIS_TRIGGERRIGHT)) + S32 total_axis = mAxesAccumulator[LLGameControl::AXIS_TRIGGERLEFT] + - mAxesAccumulator[LLGameControl::AXIS_TRIGGERRIGHT]; + if (channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) { // negate previous math when TRIGGERRIGHT is positive channel total_axis *= -1; @@ -692,7 +1354,7 @@ void LLGameControllerManager::getFlycamInputs(std::vector<F32>& inputs) // value arrives as S16 in range [-32768, 32767] // so we scale positive and negative values by slightly different factors // to try to map it to [-1, 1] - F32 input = F32(axis) * ((axis > 0.0f) ? 3.051850476e-5 : 3.0517578125e-5f) * channel.mSign; + F32 input = F32(axis) / ((axis > 0.0f) ? 32767 : 32768) * channel.mSign; inputs.push_back(input); } } @@ -737,7 +1399,7 @@ void LLGameControllerManager::setExternalInput(U32 action_flags, U32 buttons) void LLGameControllerManager::clear() { - mStates.clear(); + mDevices.clear(); } U64 get_now_nsec() @@ -748,47 +1410,67 @@ U64 get_now_nsec() void onJoystickDeviceAdded(const SDL_Event& event) { - LL_INFOS("GameController") << "device index: " << event.cdevice.which << LL_ENDL; + SDL_JoystickGUID guid(SDL_JoystickGetDeviceGUID(event.cdevice.which)); + SDL_JoystickType type(SDL_JoystickGetDeviceType(event.cdevice.which)); + std::string name(std::to_string(SDL_JoystickNameForIndex(event.cdevice.which))); + std::string path(std::to_string(SDL_JoystickPathForIndex(event.cdevice.which))); + + LL_INFOS("SDL2") << "joystick {id:" << event.cdevice.which + << ",guid:'" << guid << "'" + << ",type:'" << type << "'" + << ",name:'" << name << "'" + << ",path:'" << path << "'" + << "}" << LL_ENDL; if (SDL_Joystick* joystick = SDL_JoystickOpen(event.cdevice.which)) { - LL_INFOS("GameController") << "joystick: " << joystick << LL_ENDL; + LL_INFOS("SDL2") << "joystick " << joystick << LL_ENDL; } else { - LL_WARNS("GameController") << "Can't open joystick: " << SDL_GetError() << LL_ENDL; + LL_WARNS("SDL2") << "Can't open joystick: " << SDL_GetError() << LL_ENDL; } } void onJoystickDeviceRemoved(const SDL_Event& event) { - LL_INFOS("GameController") << "joystick id: " << event.cdevice.which << LL_ENDL; + LL_INFOS("SDL2") << "joystick id: " << event.cdevice.which << LL_ENDL; } void onControllerDeviceAdded(const SDL_Event& event) { - LL_INFOS("GameController") << "device index: " << event.cdevice.which << LL_ENDL; + std::string guid(std::to_string(SDL_JoystickGetDeviceGUID(event.cdevice.which))); + SDL_GameControllerType type(SDL_GameControllerTypeForIndex(event.cdevice.which)); + std::string name(std::to_string(SDL_GameControllerNameForIndex(event.cdevice.which))); + std::string path(std::to_string(SDL_GameControllerPathForIndex(event.cdevice.which))); + + LL_INFOS("SDL2") << "controller {id:" << event.cdevice.which + << ",guid:'" << guid << "'" + << ",type:'" << type << "'" + << ",name:'" << name << "'" + << ",path:'" << path << "'" + << "}" << LL_ENDL; SDL_JoystickID id = SDL_JoystickGetDeviceInstanceID(event.cdevice.which); if (id < 0) { - LL_WARNS("GameController") << "Can't get device instance ID: " << SDL_GetError() << LL_ENDL; + LL_WARNS("SDL2") << "Can't get device instance ID: " << SDL_GetError() << LL_ENDL; return; } SDL_GameController* controller = SDL_GameControllerOpen(event.cdevice.which); if (!controller) { - LL_WARNS("GameController") << "Can't open game controller: " << SDL_GetError() << LL_ENDL; + LL_WARNS("SDL2") << "Can't open game controller: " << SDL_GetError() << LL_ENDL; return; } - g_manager.addController(id, controller); + g_manager.addController(id, guid, name); } void onControllerDeviceRemoved(const SDL_Event& event) { - LL_INFOS("GameController") << "joystick id=" << event.cdevice.which << LL_ENDL; + LL_INFOS("SDL2") << "joystick id=" << event.cdevice.which << LL_ENDL; SDL_JoystickID id = event.cdevice.which; g_manager.removeController(id); @@ -819,40 +1501,62 @@ void sdl_logger(void *userdata, int category, SDL_LogPriority priority, const ch } // static -void LLGameControl::init(const std::string& gamecontrollerdb_path) +void LLGameControl::init(const std::string& gamecontrollerdb_path, + std::function<bool(const std::string&)> loadBoolean, + std::function<void(const std::string&, bool)> saveBoolean, + std::function<std::string(const std::string&)> loadString, + std::function<void(const std::string&, const std::string&)> saveString, + std::function<LLSD(const std::string&)> loadObject, + std::function<void(const std::string&, const LLSD&)> saveObject) { - if (!g_gameControl) + if (g_gameControl) + return; + + llassert(loadBoolean); + llassert(saveBoolean); + llassert(loadString); + llassert(saveString); + llassert(loadObject); + llassert(saveObject); + + int result = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); + if (result < 0) { - int result = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); - if (result < 0) - { - // This error is critical, we stop working with SDL and return - LL_WARNS("GameController") << "Error initializing the subsystems : " << SDL_GetError() << LL_ENDL; - return; - } + // This error is critical, we stop working with SDL and return + LL_WARNS("SDL2") << "Error initializing the subsystems : " << SDL_GetError() << LL_ENDL; + return; + } - SDL_LogSetOutputFunction(&sdl_logger, nullptr); + SDL_LogSetOutputFunction(&sdl_logger, nullptr); - // The inability to read this file is not critical, we can continue working - if (!LLFile::isfile(gamecontrollerdb_path.c_str())) + // The inability to read this file is not critical, we can continue working + if (!LLFile::isfile(gamecontrollerdb_path.c_str())) + { + LL_WARNS("SDL2") << "Device mapping db file not found: " << gamecontrollerdb_path << LL_ENDL; + } + else + { + int count = SDL_GameControllerAddMappingsFromFile(gamecontrollerdb_path.c_str()); + if (count < 0) { - LL_WARNS("GameController") << "Device mapping db file not found: " << gamecontrollerdb_path << LL_ENDL; + LL_WARNS("SDL2") << "Error adding mappings from " << gamecontrollerdb_path << " : " << SDL_GetError() << LL_ENDL; } else { - int count = SDL_GameControllerAddMappingsFromFile(gamecontrollerdb_path.c_str()); - if (count < 0) - { - LL_WARNS("GameController") << "Error adding mappings from " << gamecontrollerdb_path << " : " << SDL_GetError() << LL_ENDL; - } - else - { - LL_INFOS("GameController") << "Total " << count << " mappings added from " << gamecontrollerdb_path << LL_ENDL; - } + LL_INFOS("SDL2") << "Total " << count << " mappings added from " << gamecontrollerdb_path << LL_ENDL; } - - g_gameControl = LLGameControl::getInstance(); } + + g_gameControl = LLGameControl::getInstance(); + + s_loadBoolean = loadBoolean; + s_saveBoolean = saveBoolean; + s_loadString = loadString; + s_saveString = saveString; + s_loadObject = loadObject; + s_saveObject = saveObject; + + loadFromSettings(); } // static @@ -862,6 +1566,18 @@ void LLGameControl::terminate() SDL_Quit(); } +// static +const std::list<LLGameControl::Device>& LLGameControl::getDevices() +{ + return g_manager.mDevices; +} + +//static +const std::map<std::string, std::string>& LLGameControl::getDeviceOptions() +{ + return g_deviceOptions; +} + //static // returns 'true' if GameControlInput message needs to go out, // which will be the case for new data or resend. Call this right @@ -870,7 +1586,7 @@ void LLGameControl::terminate() bool LLGameControl::computeFinalStateAndCheckForChanges() { // Note: LLGameControllerManager::computeFinalState() modifies g_nextResendPeriod as a side-effect - g_manager.computeFinalState(g_finalState); + g_manager.computeFinalState(); // should send input when: // sending is enabled and @@ -938,32 +1654,109 @@ const LLGameControl::State& LLGameControl::getState() } // static +LLGameControl::InputChannel LLGameControl::getActiveInputChannel() +{ + InputChannel input; + + State state = g_finalState; + if (state.mButtons > 0) + { + // check buttons + input.mType = LLGameControl::InputChannel::TYPE_BUTTON; + for (U8 i = 0; i < 32; ++i) + { + if ((0x1 << i) & state.mButtons) + { + input.mIndex = i; + break; + } + } + } + else + { + // scan axes + S16 threshold = std::numeric_limits<S16>::max() / 2; + for (U8 i = 0; i < 6; ++i) + { + if (abs(state.mAxes[i]) > threshold) + { + input.mType = LLGameControl::InputChannel::TYPE_AXIS; + // input.mIndex ultimately translates to a LLGameControl::KeyboardAxis + // which distinguishes between negative and positive directions + // so we must translate to axis index "i" according to the sign + // of the axis value. + input.mIndex = i; + input.mSign = state.mAxes[i] > 0 ? 1 : -1; + break; + } + } + } + + return input; +} + +// static void LLGameControl::getFlycamInputs(std::vector<F32>& inputs_out) { return g_manager.getFlycamInputs(inputs_out); } // static -void LLGameControl::enableSendToServer(bool enable) +void LLGameControl::setSendToServer(bool enable) { g_sendToServer = enable; + s_saveBoolean(SETTING_SENDTOSERVER, g_sendToServer); } // static -void LLGameControl::enableControlAgent(bool enable) +void LLGameControl::setControlAgent(bool enable) { g_controlAgent = enable; + s_saveBoolean(SETTING_CONTROLAGENT, g_controlAgent); } // static -void LLGameControl::enableTranslateAgentActions(bool enable) +void LLGameControl::setTranslateAgentActions(bool enable) { g_translateAgentActions = enable; + s_saveBoolean(SETTING_TRANSLATEACTIONS, g_translateAgentActions); } +// static void LLGameControl::setAgentControlMode(LLGameControl::AgentControlMode mode) { g_agentControlMode = mode; + s_saveString(SETTING_AGENTCONTROLMODE, convertAgentControlModeToString(mode)); +} + +// static +bool LLGameControl::getSendToServer() +{ + return g_sendToServer; +} + +// static +bool LLGameControl::getControlAgent() +{ + return g_controlAgent; +} + +// static +bool LLGameControl::getTranslateAgentActions() +{ + return g_translateAgentActions; +} + +// static +LLGameControl::AgentControlMode LLGameControl::getAgentControlMode() +{ + return g_agentControlMode; +} + +// static +LLGameControl::ActionNameType LLGameControl::getActionNameType(const std::string& action) +{ + return g_manager.getActionNameType(action); } // static @@ -979,58 +1772,39 @@ bool LLGameControl::willControlAvatar() LLGameControl::InputChannel LLGameControl::getChannelByName(const std::string& name) { LLGameControl::InputChannel channel; + // 'name' has two acceptable formats: AXIS_<index>[sign] or BUTTON_<index> - if (name.length() < 6) - { - // name must be at least as long as 'AXIS_n' - return channel; - } - if (name.rfind("AXIS_", 0) == 0) + if (LLStringUtil::startsWith(name, "AXIS_")) { - char c = name[5]; - if (c >= '0') - { - channel.mType = LLGameControl::InputChannel::Type::TYPE_AXIS; - channel.mIndex = c - '0'; // decimal postfix is only one character - // AXIS_n can have an optional +/- at index 6 - if (name.length() >= 6) - { - channel.mSign = (name[6] == '-') ? -1 : 1; - } - else - { - // assume positive axis when sign not provided - channel.mSign = 1; - } - } + channel.mType = LLGameControl::InputChannel::Type::TYPE_AXIS; + // Decimal postfix is only one character + channel.mIndex = atoi(name.substr(5, 1).c_str()); + // AXIS_n can have an optional +/- at index 6 + // Assume positive axis when sign not provided + channel.mSign = name.back() == '-' ? -1 : 1; } - else if (name.rfind("BUTTON_", 0) == 0) + else if (LLStringUtil::startsWith(name, "BUTTON_")) { - // the BUTTON_ decimal postfix can be up to two characters wide - size_t i = 6; - U8 index = 0; - while (i < name.length() && i < 8 && name[i] >= '0') - { - index = index * 10 + name[i] - '0'; - } channel.mType = LLGameControl::InputChannel::Type::TYPE_BUTTON; - channel.mIndex = index; + // Decimal postfix is only one or two characters + channel.mIndex = atoi(name.substr(7).c_str()); } + return channel; } // static // Given an action_name like "push+", or "strafe-", returns the InputChannel // mapped to it if found, else channel.isNone() will be true. -LLGameControl::InputChannel LLGameControl::getChannelByActionName(const std::string& name) +LLGameControl::InputChannel LLGameControl::getChannelByAction(const std::string& action) { - return g_manager.getChannelByActionName(name); + return g_manager.getChannelByAction(action); } // static -bool LLGameControl::updateActionMap(const std::string& action_name, LLGameControl::InputChannel channel) +bool LLGameControl::updateActionMap(const std::string& action, LLGameControl::InputChannel channel) { - return g_manager.updateActionMap(action_name, channel); + return g_manager.updateActionMap(action, channel); } // static @@ -1070,3 +1844,239 @@ void LLGameControl::updateResendPeriod() } } +// static +std::string LLGameControl::stringifyAnalogMappings(getChannel_t getChannel) +{ + return getMappings(g_manager.mAnalogActions, InputChannel::TYPE_AXIS, getChannel); +} + +// static +std::string LLGameControl::stringifyBinaryMappings(getChannel_t getChannel) +{ + return getMappings(g_manager.mBinaryActions, InputChannel::TYPE_BUTTON, getChannel); +} + +// static +std::string LLGameControl::stringifyFlycamMappings(getChannel_t getChannel) +{ + return getMappings(g_manager.mFlycamActions, InputChannel::TYPE_AXIS, getChannel); +} + +// static +void LLGameControl::getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings) +{ + g_manager.getDefaultMappings(mappings); +} + +// static +bool LLGameControl::parseDeviceOptions(const std::string& options, std::string& name, + std::vector<LLGameControl::Options::AxisOptions>& axis_options, + std::vector<U8>& axis_map, std::vector<U8>& button_map) +{ + if (options.empty()) + return false; + + name.clear(); + axis_options.resize(NUM_AXES); + axis_map.resize(NUM_AXES); + button_map.resize(NUM_BUTTONS); + + for (size_t i = 0; i < NUM_AXES; ++i) + { + axis_options[i].resetToDefaults(); + axis_map[i] = (U8)i; + } + + for (size_t i = 0; i < NUM_BUTTONS; ++i) + { + button_map[i] = (U8)i; + } + + std::map<std::string, std::string> pairs; + if (!parse(pairs, options)) + { + LL_WARNS("SDL2") << "Invalid options: '" << options << "'" << LL_ENDL; + return false; + } + + std::map<std::string, std::string> axis_string_options; + if (!parse(axis_string_options, pairs["axis_options"])) + { + LL_WARNS("SDL2") << "Invalid axis_options: '" << pairs["axis_options"] << "'" << LL_ENDL; + return false; + } + + std::map<std::string, std::string> axis_string_map; + if (!parse(axis_string_map, pairs["axis_map"])) + { + LL_WARNS("SDL2") << "Invalid axis_map: '" << pairs["axis_map"] << "'" << LL_ENDL; + return false; + } + + std::map<std::string, std::string> button_string_map; + if (!parse(button_string_map, pairs["button_map"])) + { + LL_WARNS("SDL2") << "Invalid button_map: '" << pairs["button_map"] << "'" << LL_ENDL; + return false; + } + + name = pairs["name"]; + + for (size_t i = 0; i < NUM_AXES; ++i) + { + std::string key = std::to_string(i); + + std::string one_axis_options = axis_string_options[key]; + if (!one_axis_options.empty()) + { + axis_options[i].loadFromString(one_axis_options); + } + + std::string value = axis_string_map[key]; + if (!value.empty()) + { + size_t number = std::stoull(value); + if (number >= NUM_AXES || std::to_string(number) != value) + { + LL_WARNS("SDL2") << "Invalid axis mapping: " << i << "->" << value << LL_ENDL; + } + else + { + axis_map[i] = (U8)number; + } + } + } + + for (size_t i = 0; i < NUM_BUTTONS; ++i) + { + std::string value = button_string_map[std::to_string(i)]; + if (!value.empty()) + { + size_t number = std::stoull(value); + if (number >= NUM_BUTTONS || std::to_string(number) != value) + { + LL_WARNS("SDL2") << "Invalid button mapping: " << i << "->" << value << LL_ENDL; + } + else + { + button_map[i] = (U8)number; + } + } + } + + return true; +} + +// static +std::string LLGameControl::stringifyDeviceOptions(const std::string& name, + const std::vector<LLGameControl::Options::AxisOptions>& axis_options, + const std::vector<U8>& axis_map, const std::vector<U8>& button_map, + bool force_empty) +{ + std::list<std::string> options; + + auto opts2str = [](size_t i, const Options::AxisOptions& options) -> std::string + { + std::string string = options.saveToString(); + return string.empty() ? string : llformat("%u:%s", i, string.c_str()); + }; + + std::string axis_options_string = LLStringUtil::join<std::vector<Options::AxisOptions>, Options::AxisOptions>(axis_options, opts2str); + if (!axis_options_string.empty()) + { + options.push_back("axis_options:{" + axis_options_string + "}"); + } + + auto map2str = [](size_t index, const U8& value) -> std::string + { + return value == index ? LLStringUtil::null : llformat("%u:%u", index, value); + }; + + std::string axis_map_string = LLStringUtil::join<std::vector<U8>, U8>(axis_map, map2str); + if (!axis_map_string.empty()) + { + options.push_back("axis_map:{" + axis_map_string + "}"); + } + + std::string button_map_string = LLStringUtil::join<std::vector<U8>, U8>(button_map, map2str); + if (!button_map_string.empty()) + { + options.push_back("button_map:{" + button_map_string + "}"); + } + + if (!force_empty && options.empty()) + return LLStringUtil::null; + + // Remove control characters [',', '{', '}'] from name + std::string safe_name; + safe_name.reserve(name.size()); + for (char c : name) + { + if (c != ',' && c != '{' && c != '}') + { + safe_name.push_back(c); + } + } + options.push_front(llformat("name:%s", safe_name.c_str())); + + std::string result = LLStringUtil::join(options); + + return "{" + result + "}"; +} + +// static +void LLGameControl::initByDefault() +{ + g_sendToServer = false; + g_controlAgent = false; + g_translateAgentActions = false; + g_agentControlMode = CONTROL_MODE_AVATAR; + g_manager.initializeMappingsByDefault(); + g_manager.resetDeviceOptionsToDefaults(); + g_deviceOptions.clear(); +} + +// static +void LLGameControl::loadFromSettings() +{ + // In case of absence of the required setting the default value is assigned + g_sendToServer = s_loadBoolean(SETTING_SENDTOSERVER); + g_controlAgent = s_loadBoolean(SETTING_CONTROLAGENT); + g_translateAgentActions = s_loadBoolean(SETTING_TRANSLATEACTIONS); + g_agentControlMode = convertStringToAgentControlMode(s_loadString(SETTING_AGENTCONTROLMODE)); + + g_manager.initializeMappingsByDefault(); + + // Load action-to-channel mappings + std::string analogMappings = s_loadString(SETTING_ANALOGMAPPINGS); + std::string binaryMappings = s_loadString(SETTING_BINARYMAPPINGS); + std::string flycamMappings = s_loadString(SETTING_FLYCAMMAPPINGS); + g_manager.setAnalogMappings(analogMappings); + g_manager.setBinaryMappings(binaryMappings); + g_manager.setFlycamMappings(flycamMappings); + + // Load device-specific settings + g_deviceOptions.clear(); + LLSD options = s_loadObject(SETTING_KNOWNCONTROLLERS); + for (auto it = options.beginMap(); it != options.endMap(); ++it) + { + g_deviceOptions.emplace(it->first, it->second); + } + g_manager.loadDeviceOptionsFromSettings(); +} + +// static +void LLGameControl::saveToSettings() +{ + s_saveBoolean(SETTING_SENDTOSERVER, g_sendToServer); + s_saveBoolean(SETTING_CONTROLAGENT, g_controlAgent); + s_saveBoolean(SETTING_TRANSLATEACTIONS, g_translateAgentActions); + s_saveString(SETTING_AGENTCONTROLMODE, convertAgentControlModeToString(g_agentControlMode)); + s_saveString(SETTING_ANALOGMAPPINGS, g_manager.getAnalogMappings()); + s_saveString(SETTING_BINARYMAPPINGS, g_manager.getBinaryMappings()); + s_saveString(SETTING_FLYCAMMAPPINGS, g_manager.getFlycamMappings()); + + g_manager.saveDeviceOptionsToSettings(); + LLSD deviceOptions(g_deviceOptions, true); + s_saveObject(SETTING_KNOWNCONTROLLERS, deviceOptions); +} diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h index 104ae3a2c6..d75aa3a018 100644 --- a/indra/llwindow/llgamecontrol.h +++ b/indra/llwindow/llgamecontrol.h @@ -79,20 +79,30 @@ public: CONTROL_MODE_NONE }; - enum KeyboardAxis + enum ActionNameType { - AXIS_LEFTX = 0, + ACTION_NAME_UNKNOWN, + ACTION_NAME_ANALOG, // E.g., "push" + ACTION_NAME_ANALOG_POS, // E.g., "push+" + ACTION_NAME_ANALOG_NEG, // E.g., "push-" + ACTION_NAME_BINARY, // E.g., "stop" + ACTION_NAME_FLYCAM // E.g., "zoom" + }; + + enum KeyboardAxis : U8 + { + AXIS_LEFTX, AXIS_LEFTY, AXIS_RIGHTX, AXIS_RIGHTY, AXIS_TRIGGERLEFT, AXIS_TRIGGERRIGHT, - AXIS_LAST + NUM_AXES }; enum Button { - BUTTON_A = 0, + BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y, @@ -123,17 +133,21 @@ public: BUTTON_28, BUTTON_29, BUTTON_30, - BUTTON_31 + BUTTON_31, + NUM_BUTTONS }; + static const U16 MAX_AXIS_DEAD_ZONE = 16384; + static const U16 MAX_AXIS_OFFSET = 16384; + class InputChannel { public: enum Type { + TYPE_NONE, TYPE_AXIS, - TYPE_BUTTON, - TYPE_NONE + TYPE_BUTTON }; InputChannel() {} @@ -141,11 +155,16 @@ public: InputChannel(Type type, U8 index, S32 sign) : mType(type), mSign(sign), mIndex(index) {} // these methods for readability + bool isNone() const { return mType == TYPE_NONE; } bool isAxis() const { return mType == TYPE_AXIS; } bool isButton() const { return mType == TYPE_BUTTON; } - bool isNone() const { return mType == TYPE_NONE; } - std::string getLocalName() const; // AXIS_0-, AXIS_0+, BUTTON_0, etc + bool isEqual(const InputChannel& other) + { + return mType == other.mType && mSign == other.mSign && mIndex == other.mIndex; + } + + std::string getLocalName() const; // AXIS_0-, AXIS_0+, BUTTON_0, NONE etc. std::string getRemoteName() const; // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc Type mType { TYPE_NONE }; @@ -153,15 +172,58 @@ public: U8 mIndex { 255 }; }; + // Options is a data structure for storing device-specific settings + class Options + { + public: + struct AxisOptions + { + bool mInvert { false }; + U16 mDeadZone { 0 }; + S16 mOffset { 0 }; + + void resetToDefaults() + { + mInvert = false; + mDeadZone = 0; + mOffset = 0; + } + + std::string saveToString() const; + void loadFromString(std::string options); + }; + + Options(); + + void resetToDefaults(); + + U8 mapAxis(U8 axis) const; + U8 mapButton(U8 button) const; + + S16 fixAxisValue(U8 axis, S16 value) const; + + std::string saveToString(const std::string& name, bool force_empty = false) const; + bool loadFromString(std::string& name, std::string options); + bool loadFromString(std::string options); + + const std::vector<AxisOptions>& getAxisOptions() const { return mAxisOptions; } + std::vector<AxisOptions>& getAxisOptions() { return mAxisOptions; } + const std::vector<U8>& getAxisMap() const { return mAxisMap; } + std::vector<U8>& getAxisMap() { return mAxisMap; } + const std::vector<U8>& getButtonMap() const { return mButtonMap; } + std::vector<U8>& getButtonMap() { return mButtonMap; } + + private: + std::vector<AxisOptions> mAxisOptions; + std::vector<U8> mAxisMap; + std::vector<U8> mButtonMap; + }; + // State is a minimal class for storing axes and buttons values class State { - int mJoystickID { -1 }; - void* mController { nullptr }; public: State(); - void setDevice(int joystickID, void* controller); - int getJoystickID() const { return mJoystickID; } void clear(); bool onButton(U8 button, bool pressed); std::vector<S16> mAxes; // [ -32768, 32767 ] @@ -169,10 +231,43 @@ public: U32 mButtons; }; + // Device is a data structure for describing any detected controller + class Device + { + const int mJoystickID { -1 }; + const std::string mGUID; + const std::string mName; + Options mOptions; + State mState; + + public: + Device(int joystickID, const std::string& guid, const std::string& name); + int getJoystickID() const { return mJoystickID; } + std::string getGUID() const { return mGUID; } + std::string getName() const { return mName; } + const Options& getOptions() const { return mOptions; } + const State& getState() const { return mState; } + + void resetOptionsToDefaults() { mOptions.resetToDefaults(); } + std::string saveOptionsToString(bool force_empty = false) const { return mOptions.saveToString(mName, force_empty); } + void loadOptionsFromString(const std::string& options) { mOptions.loadFromString(options); } + + friend class LLGameControllerManager; + }; + static bool isInitialized(); - static void init(const std::string& gamecontrollerdb_path); + static void init(const std::string& gamecontrollerdb_path, + std::function<bool(const std::string&)> loadBoolean, + std::function<void(const std::string&, bool)> saveBoolean, + std::function<std::string(const std::string&)> loadString, + std::function<void(const std::string&, const std::string&)> saveString, + std::function<LLSD(const std::string&)> loadObject, + std::function<void(const std::string&, const LLSD&)> saveObject); static void terminate(); + static const std::list<LLGameControl::Device>& getDevices(); + static const std::map<std::string, std::string>& getDeviceOptions(); + // returns 'true' if GameControlInput message needs to go out, // which will be the case for new data or resend. Call this right // before deciding to put a GameControlInput packet on the wire @@ -183,14 +278,21 @@ public: static void processEvents(bool app_has_focus = true); static const State& getState(); + static InputChannel getActiveInputChannel(); static void getFlycamInputs(std::vector<F32>& inputs_out); // these methods for accepting input from keyboard - static void enableSendToServer(bool enable); - static void enableControlAgent(bool enable); - static void enableTranslateAgentActions(bool enable); + static void setSendToServer(bool enable); + static void setControlAgent(bool enable); + static void setTranslateAgentActions(bool enable); static void setAgentControlMode(AgentControlMode mode); + static bool getSendToServer(); + static bool getControlAgent(); + static bool getTranslateAgentActions(); + static AgentControlMode getAgentControlMode(); + static ActionNameType getActionNameType(const std::string& action); + static bool willControlAvatar(); // Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel @@ -198,7 +300,7 @@ public: static LLGameControl::InputChannel getChannelByName(const std::string& name); // action_name = push+, strafe-, etc - static LLGameControl::InputChannel getChannelByActionName(const std::string& name); + static LLGameControl::InputChannel getChannelByAction(const std::string& action); static bool updateActionMap(const std::string& action_name, LLGameControl::InputChannel channel); @@ -210,5 +312,23 @@ public: // call this after putting a GameControlInput packet on the wire static void updateResendPeriod(); + + using getChannel_t = std::function<LLGameControl::InputChannel(const std::string& action)>; + static std::string stringifyAnalogMappings(getChannel_t getChannel); + static std::string stringifyBinaryMappings(getChannel_t getChannel); + static std::string stringifyFlycamMappings(getChannel_t getChannel); + static void getDefaultMappings(std::vector<std::pair<std::string, LLGameControl::InputChannel>>& mappings); + + static bool parseDeviceOptions(const std::string& options, std::string& name, + std::vector<LLGameControl::Options::AxisOptions>& axis_options, + std::vector<U8>& axis_map, std::vector<U8>& button_map); + static std::string stringifyDeviceOptions(const std::string& name, + const std::vector<LLGameControl::Options::AxisOptions>& axis_options, + const std::vector<U8>& axis_map, const std::vector<U8>& button_map, + bool force_empty = false); + + static void initByDefault(); + static void loadFromSettings(); + static void saveToSettings(); }; diff --git a/indra/llwindow/llgamecontroltranslator.cpp b/indra/llwindow/llgamecontroltranslator.cpp index 9654cb04f1..84468ab657 100644 --- a/indra/llwindow/llgamecontroltranslator.cpp +++ b/indra/llwindow/llgamecontroltranslator.cpp @@ -40,7 +40,7 @@ LLGameControlTranslator::LLGameControlTranslator() { } -void LLGameControlTranslator::setAvailableActions(ActionToMaskMap& action_to_mask) +void LLGameControlTranslator::setAvailableActionMasks(ActionToMaskMap& action_to_mask) { mActionToMask = std::move(action_to_mask); } @@ -48,11 +48,11 @@ void LLGameControlTranslator::setAvailableActions(ActionToMaskMap& action_to_mas LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const std::string& action) const { LLGameControl::InputChannel channel; - ActionToMaskMap::const_iterator mask_itr = mActionToMask.find(action); + auto mask_itr = mActionToMask.find(action); if (mask_itr != mActionToMask.end()) { U32 mask = mask_itr->second; - LLGameControlTranslator::MaskToChannelMap::const_iterator channel_itr = mMaskToChannel.find(mask); + auto channel_itr = mMaskToChannel.find(mask); if (channel_itr != mMaskToChannel.end()) { channel = channel_itr->second; @@ -68,7 +68,7 @@ LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const st if (mask_itr != mActionToMask.end()) { U32 mask = mask_itr->second; - LLGameControlTranslator::MaskToChannelMap::const_iterator channel_itr = mMaskToChannel.find(mask); + auto channel_itr = mMaskToChannel.find(mask); if (channel_itr != mMaskToChannel.end()) { channel = channel_itr->second; @@ -78,140 +78,95 @@ LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const st return channel; } -void LLGameControlTranslator::setMappings(LLGameControlTranslator::NamedChannels& list) +void LLGameControlTranslator::setMappings(LLGameControlTranslator::NamedChannels& named_channels) { mMaskToChannel.clear(); mMappedFlags = 0; mPrevActiveFlags = 0; mCachedState.clear(); - for (auto& name_channel : list) + for (const auto& named_channel : named_channels) { - updateMap(name_channel.first, name_channel.second); + updateMap(named_channel.first, named_channel.second); } } -bool LLGameControlTranslator::updateMap(const std::string& name, const LLGameControl::InputChannel& channel) +void LLGameControlTranslator::updateMap(const std::string& action, const LLGameControl::InputChannel& channel) { - bool map_changed = false; - size_t name_length = name.length(); - if (name_length > 1) + // First, get the action name type + LLGameControl::ActionNameType actionNameType = LLGameControl::getActionNameType(action); + if (actionNameType == LLGameControl::ACTION_NAME_UNKNOWN || + actionNameType == LLGameControl::ACTION_NAME_FLYCAM) { - if (channel.isButton()) - { - map_changed = updateMapInternal(name, channel); - } - else if (channel.isAxis()) + LL_WARNS("SDL2") << "unmappable action='" << action << "' (type=" << actionNameType << ")" << LL_ENDL; + return; + } + + // Second, get the expected associated channel type (except of TYPE_NONE) + LLGameControl::InputChannel::Type expectedChannelType = + actionNameType == LLGameControl::ACTION_NAME_BINARY ? + LLGameControl::InputChannel::TYPE_BUTTON : + LLGameControl::InputChannel::TYPE_AXIS; + if (!channel.isNone() && (channel.mType != expectedChannelType)) + { + LL_WARNS("SDL2") << "unmappable channel (type=" << channel.mType << ")" + << " for action='" << action << "' (type=" << actionNameType << ")" << LL_ENDL; + return; + } + + if (actionNameType == LLGameControl::ACTION_NAME_ANALOG) // E.g., "push" + { + // Special (double) processing for analog action names + // sequentially adding '+' and '-' to the given action + updateMapInternal(action + "+", channel); + + // For the channel type TYPE_NONE we can map the same channel + // In fact, the mapping will be removed from the mapping list + if (channel.isNone()) { - U8 last_char = name.at(name_length - 1); - if (last_char == '+' || last_char == '-') - { - map_changed = updateMapInternal(name, channel); - } - else - { - // try to map both "name+" and "name-" - std::string new_name = name; - new_name.append("+"); - bool success = updateMapInternal(new_name, channel); - if (success) - { - new_name.data()[name_length] = '-'; - LLGameControl::InputChannel other_channel(channel.mType, channel.mIndex, -channel.mSign); - // TIED TRIGGER HACK: this works for XBox and similar controllers, - // and those are pretty much the only supported devices right now - // however TODO: figure out how to do this better. - // - // AXIS_TRIGGERLEFT and AXIS_TRIGGERRIGHT are separate axes and most devices - // only allow them to read positive, not negative. When used for motion control - // they are typically paired together. We assume as much here when computing - // the other_channel. - if (channel.mIndex == LLGameControl::AXIS_TRIGGERLEFT) - { - other_channel.mIndex = LLGameControl::AXIS_TRIGGERRIGHT; - other_channel.mSign = 1; - } - else if (channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) - { - other_channel.mIndex = LLGameControl::AXIS_TRIGGERLEFT; - other_channel.mSign = 1; - } - updateMapInternal(new_name, other_channel); - map_changed = true; - } - } + updateMapInternal(action + "-", channel); } else { - // channel type is NONE, which means the action needs to be removed from the map - // but we don't know if it mapped to button or axis which is important because - // it if it axis then we need to also remove the other entry. - // So we try to look it up - ActionToMaskMap::iterator mask_itr = mActionToMask.find(name); - if (mask_itr != mActionToMask.end()) - { - // we found the action --> was it mapped to an axis? - bool is_axis = false; - U32 mask = mask_itr->second; - LLGameControlTranslator::MaskToChannelMap::iterator channel_itr = mMaskToChannel.find(mask); - if (channel_itr != mMaskToChannel.end()) - { - if (channel_itr->second.isAxis()) - { - // yes, it is an axis - is_axis = true; - } - } - // remove from map, whether button or axis - updateMapInternal(name, channel); + // For the channel type except of TYPE_NONE we construct the other channel + // with the same type and index but with the opposite sign + LLGameControl::InputChannel other_channel(channel.mType, channel.mIndex, -channel.mSign); - if (is_axis) - { - // also need to remove the other entry - std::string other_name = name; - if (other_name.data()[name.length() - 1] == '-') - { - other_name.data()[name.length() - 1] = '+'; - } - else - { - other_name.data()[name.length() - 1] = '-'; - } - // remove from map - updateMapInternal(other_name, channel); - } + // TIED TRIGGER HACK: this works for XBox and similar controllers, + // and those are pretty much the only supported devices right now + // however TODO: figure out how to do this better. + // + // AXIS_TRIGGERLEFT and AXIS_TRIGGERRIGHT are separate axes and most devices + // only allow them to read positive, not negative. When used for motion control + // they are typically paired together. We assume as much here when computing + // the other_channel. + if (channel.mIndex == LLGameControl::AXIS_TRIGGERLEFT) + { + other_channel.mIndex = LLGameControl::AXIS_TRIGGERRIGHT; + other_channel.mSign = 1; } - else if (name.data()[name.length() - 1] == '+' - || name.data()[name.length() - 1] == '-') + else if (channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT) { - // action was not found but name doesn't end with +/- - // maybe it is an axis-name sans the +/- on the end - // postfix with '+' and try again - std::string other_name = name; - other_name.append("+"); - map_changed = updateMapInternal(other_name, channel); - if (map_changed) - { - // that worked! now do the other one - other_name.data()[name.length()] = '-'; - updateMapInternal(other_name, channel); - } + other_channel.mIndex = LLGameControl::AXIS_TRIGGERLEFT; + other_channel.mSign = 1; } + updateMapInternal(action + "-", other_channel); } } + else + { + // Simple (single) processing for other action name types + updateMapInternal(action, channel); + } - if (map_changed) + // recompute mMappedFlags + mMappedFlags = 0; + for (auto& pair : mMaskToChannel) { - // recompute mMappedFlags - mMappedFlags = 0; - for (auto& pair : mMaskToChannel) - { - mMappedFlags |= pair.first; - } - mPrevActiveFlags = 0; - mCachedState.clear(); + mMappedFlags |= pair.first; } - return map_changed; + mPrevActiveFlags = 0; + mCachedState.clear(); } // Given external action_flags (i.e. raw avatar input) @@ -249,7 +204,7 @@ const LLGameControl::State& LLGameControlTranslator::computeStateFromFlags(U32 a } else if (channel.isButton()) { - mCachedState.mButtons |= (0x01U << channel.mIndex); + mCachedState.mButtons |= 0x01U << channel.mIndex; } } } @@ -295,46 +250,26 @@ U32 LLGameControlTranslator::computeFlagsFromState(const std::vector<S32>& axes, return action_flags; } -bool LLGameControlTranslator::updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel) +void LLGameControlTranslator::updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel) { - bool something_changed = false; - ActionToMaskMap::iterator mask_itr = mActionToMask.find(name); - if (mask_itr != mActionToMask.end()) + auto action_it = mActionToMask.find(name); + llassert(action_it != mActionToMask.end()); + U32 mask = action_it->second; + auto channel_it = mMaskToChannel.find(mask); + if (channel_it == mMaskToChannel.end()) { - U32 mask = mask_itr->second; - something_changed = addOrRemoveMaskMapping(mask, channel); + // create new mapping + mMaskToChannel[mask] = channel; } - return something_changed; -} - -bool LLGameControlTranslator::addOrRemoveMaskMapping(U32 mask, const LLGameControl::InputChannel& channel) -{ - bool success = false; - LLGameControlTranslator::MaskToChannelMap::iterator channel_itr = mMaskToChannel.find(mask); - if (channel_itr != mMaskToChannel.end()) + else if (channel.isNone()) { - LLGameControl::InputChannel old_channel = channel_itr->second; - if (old_channel.mType != channel.mType || old_channel.mIndex != channel.mIndex || old_channel.mSign != channel.mSign) - { - if (channel.isNone()) - { - // remove old mapping - mMaskToChannel.erase(channel_itr); - } - else - { - // update old mapping - channel_itr->second = channel; - } - success = true; - } + // remove old mapping + mMaskToChannel.erase(channel_it); } - else if (! channel.isNone()) + else { - // create new mapping - mMaskToChannel[mask] = channel; - success = true; + // update old mapping + channel_it->second = channel; } - return success; } diff --git a/indra/llwindow/llgamecontroltranslator.h b/indra/llwindow/llgamecontroltranslator.h index f47c4f4a5f..533408014c 100644 --- a/indra/llwindow/llgamecontroltranslator.h +++ b/indra/llwindow/llgamecontroltranslator.h @@ -54,10 +54,10 @@ public: LLGameControlTranslator(); - void setAvailableActions(ActionToMaskMap& action_to_mask); + void setAvailableActionMasks(ActionToMaskMap& action_to_mask); LLGameControl::InputChannel getChannelByAction(const std::string& action) const; - void setMappings(NamedChannels& list); - bool updateMap(const std::string& name, const LLGameControl::InputChannel& channel); + void setMappings(NamedChannels& named_channels); + void updateMap(const std::string& action, const LLGameControl::InputChannel& channel); // Note: to remove a mapping you can call updateMap() with a TYPE_NONE channel // Given external action_flags (i.e. raw avatar input) @@ -72,8 +72,7 @@ public: U32 getMappedFlags() const { return mMappedFlags; } private: - bool updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel); - bool addOrRemoveMaskMapping(U32 mask, const LLGameControl::InputChannel& channel); + void updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel); private: // mActionToMask is an invarient map between the possible actions diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index 7784e6c32a..44679d3843 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -276,40 +276,58 @@ bool LLKeyboard::handleTranslatedKeyUp(KEY translated_key, U32 translated_mask) return handled; } +<<<<<<< HEAD bool LLKeyboard::handleKeyDown(const U16 key, const U32 mask) { U32 translated_mask = updateModifiers(mask); +======= +bool LLKeyboard::handleKeyDown(const U16 key, const MASK mask) +{ + U32 translated_mask = updateModifiers(mask); +>>>>>>> 7733b56eab (Add GameControl UI for per device settings) KEY translated_key = 0; bool handled = false; if(translateKey(key, &translated_key)) { handled = handleTranslatedKeyDown(translated_key, translated_mask); } +<<<<<<< HEAD if (!handled) { LLGameControl::onKeyDown(translated_key, translated_mask); } +======= +>>>>>>> 7733b56eab (Add GameControl UI for per device settings) return handled; } +<<<<<<< HEAD bool LLKeyboard::handleKeyUp(const U16 key, const U32 mask) { U32 translated_mask = updateModifiers(mask); +======= +bool LLKeyboard::handleKeyUp(const U16 key, const MASK mask) +{ + U32 translated_mask = updateModifiers(mask); +>>>>>>> 7733b56eab (Add GameControl UI for per device settings) KEY translated_key = 0; bool handled = false; if(translateKey(key, &translated_key)) { handled = handleTranslatedKeyUp(translated_key, translated_mask); } +<<<<<<< HEAD if (!handled) { LLGameControl::onKeyUp(translated_key, translated_mask); } +======= +>>>>>>> 7733b56eab (Add GameControl UI for per device settings) return handled; } diff --git a/indra/llwindow/llkeyboard.h b/indra/llwindow/llkeyboard.h index d3c35b1ed4..d91e023b85 100644 --- a/indra/llwindow/llkeyboard.h +++ b/indra/llwindow/llkeyboard.h @@ -80,10 +80,7 @@ public: virtual bool handleKeyUp(const NATIVE_KEY_TYPE key, MASK mask) = 0; virtual bool handleKeyDown(const NATIVE_KEY_TYPE key, MASK mask) = 0; -#ifdef LL_DARWIN - // We only actually use this for macOS. - virtual void handleModifier(MASK mask) = 0; -#endif // LL_DARWIN + virtual void handleModifier(MASK mask) { } // Asynchronously poll the control, alt, and shift keys and set the // appropriate internal key masks. @@ -113,6 +110,7 @@ public: protected: void addKeyName(KEY key, const std::string& name); + virtual MASK updateModifiers(const MASK mask) { return mask; } protected: std::map<NATIVE_KEY_TYPE, KEY> mTranslateKeyMap; // Map of translations from OS keys to Linden KEYs diff --git a/indra/llwindow/llkeyboardmacosx.cpp b/indra/llwindow/llkeyboardmacosx.cpp index 4ce98ee32b..1a403e5d94 100644 --- a/indra/llwindow/llkeyboardmacosx.cpp +++ b/indra/llwindow/llkeyboardmacosx.cpp @@ -162,7 +162,7 @@ LLKeyboardMacOSX::LLKeyboardMacOSX() void LLKeyboardMacOSX::resetMaskKeys() { - U32 mask = getModifiers(); + MASK mask = getModifiers(); // MBW -- XXX -- This mirrors the operation of the Windows version of resetMaskKeys(). // It looks a bit suspicious, as it won't correct for keys that have been released. @@ -187,7 +187,7 @@ void LLKeyboardMacOSX::resetMaskKeys() } /* -static bool translateKeyMac(const U16 key, const U32 mask, KEY &outKey, U32 &outMask) +static bool translateKeyMac(const U16 key, const MASK mask, KEY &outKey, U32 &outMask) { // Translate the virtual keycode into the keycodes the keyboard system expects. U16 virtualKey = (mask >> 24) & 0x0000007F; @@ -203,7 +203,7 @@ void LLKeyboardMacOSX::handleModifier(MASK mask) updateModifiers(mask); } -MASK LLKeyboardMacOSX::updateModifiers(U32 mask) +MASK LLKeyboardMacOSX::updateModifiers(const MASK mask) { // translate the mask MASK out_mask = 0; @@ -226,7 +226,7 @@ MASK LLKeyboardMacOSX::updateModifiers(U32 mask) return out_mask; } -bool LLKeyboardMacOSX::handleKeyDown(const U16 key, const U32 mask) +bool LLKeyboardMacOSX::handleKeyDown(const U16 key, MASK mask) { KEY translated_key = 0; U32 translated_mask = 0; @@ -243,7 +243,7 @@ bool LLKeyboardMacOSX::handleKeyDown(const U16 key, const U32 mask) } -bool LLKeyboardMacOSX::handleKeyUp(const U16 key, const U32 mask) +bool LLKeyboardMacOSX::handleKeyUp(const U16 key, MASK mask) { KEY translated_key = 0; U32 translated_mask = 0; @@ -262,7 +262,7 @@ bool LLKeyboardMacOSX::handleKeyUp(const U16 key, const U32 mask) MASK LLKeyboardMacOSX::currentMask(bool for_mouse_event) { MASK result = MASK_NONE; - U32 mask = getModifiers(); + MASK mask = getModifiers(); if (mask & MAC_SHIFT_KEY) result |= MASK_SHIFT; if (mask & MAC_CTRL_KEY) result |= MASK_CONTROL; diff --git a/indra/llwindow/llkeyboardmacosx.h b/indra/llwindow/llkeyboardmacosx.h index af8a626db8..a5f59f3fba 100644 --- a/indra/llwindow/llkeyboardmacosx.h +++ b/indra/llwindow/llkeyboardmacosx.h @@ -52,7 +52,7 @@ public: void handleModifier(MASK mask) override; protected: - MASK updateModifiers(const U32 mask); + MASK updateModifiers(const MASK mask) override; void setModifierKeyLevel( KEY key, bool new_state ); bool translateNumpadKey( const U16 os_key, KEY *translated_key ); U16 inverseTranslateNumpadKey(const KEY translated_key); diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp index 636eaa5491..b8b2b311f7 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -177,7 +177,7 @@ void LLKeyboardSDL::resetMaskKeys() } -MASK LLKeyboardSDL::updateModifiers(const U32 mask) +MASK LLKeyboardSDL::updateModifiers(const MASK mask) { // translate the mask MASK out_mask = MASK_NONE; @@ -201,7 +201,7 @@ MASK LLKeyboardSDL::updateModifiers(const U32 mask) } -static U32 adjustNativekeyFromUnhandledMask(const U32 key, const U32 mask) +static U32 adjustNativekeyFromUnhandledMask(const U16 key, const MASK mask) { // SDL doesn't automatically adjust the keysym according to // whether NUMLOCK is engaged, so we massage the keysym manually. @@ -226,7 +226,7 @@ static U32 adjustNativekeyFromUnhandledMask(const U32 key, const U32 mask) } -bool LLKeyboardSDL::handleKeyDown(const U32 key, const U32 mask) +bool LLKeyboardSDL::handleKeyDown(const U32 key, const MASK mask) { U32 adjusted_nativekey; KEY translated_key = 0; @@ -246,7 +246,7 @@ bool LLKeyboardSDL::handleKeyDown(const U32 key, const U32 mask) } -bool LLKeyboardSDL::handleKeyUp(const U32 key, const U32 mask) +bool LLKeyboardSDL::handleKeyUp(const U32 key, const MASK mask) { U32 adjusted_nativekey; KEY translated_key = 0; diff --git a/indra/llwindow/llkeyboardsdl.h b/indra/llwindow/llkeyboardsdl.h index 9ebff66865..fb08fd218b 100644 --- a/indra/llwindow/llkeyboardsdl.h +++ b/indra/llwindow/llkeyboardsdl.h @@ -42,7 +42,7 @@ public: /*virtual*/ void scanKeyboard(); protected: - MASK updateModifiers(const U32 mask); + MASK updateModifiers(const MASK mask) override; void setModifierKeyLevel( KEY key, bool new_state ); bool translateNumpadKey( const U32 os_key, KEY *translated_key ); U16 inverseTranslateNumpadKey(const KEY translated_key); diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp index 756caf6fc3..c31ef5c9a3 100644 --- a/indra/llwindow/llkeyboardwin32.cpp +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -182,7 +182,7 @@ void LLKeyboardWin32::resetMaskKeys() //} -MASK LLKeyboardWin32::updateModifiers(U32 mask) +MASK LLKeyboardWin32::updateModifiers(const U32 mask) { //RN: this seems redundant, as we should have already received the appropriate // messages for the modifier keys @@ -191,8 +191,7 @@ MASK LLKeyboardWin32::updateModifiers(U32 mask) // (keydown encoded in high order bit of short) mKeyLevel[KEY_CAPSLOCK] = (GetKeyState(VK_CAPITAL) & 0x0001) != 0; // Low order bit carries the toggle state. // Get mask for keyboard events - MASK mask = currentMask(false); - return mask; + return currentMask(false); } @@ -203,7 +202,7 @@ bool LLKeyboardWin32::handleKeyDown(const U16 key, MASK mask) U32 translated_mask; bool handled = false; - translated_mask = updateModifiers(); + translated_mask = updateModifiers(mask); if (translateExtendedKey(key, mask, &translated_key)) { @@ -220,7 +219,7 @@ bool LLKeyboardWin32::handleKeyUp(const U16 key, MASK mask) U32 translated_mask; bool handled = false; - translated_mask = updateModifiers(); + translated_mask = updateModifiers(mask); if (translateExtendedKey(key, mask, &translated_key)) { diff --git a/indra/llwindow/llkeyboardwin32.h b/indra/llwindow/llkeyboardwin32.h index d3dc65d9aa..ea7bb4d866 100644 --- a/indra/llwindow/llkeyboardwin32.h +++ b/indra/llwindow/llkeyboardwin32.h @@ -49,7 +49,7 @@ public: U16 inverseTranslateExtendedKey(const KEY translated_key); protected: - MASK updateModifiers(); + MASK updateModifiers(const MASK mask) override; //void setModifierKeyLevel( KEY key, bool new_state ); private: std::map<U16, KEY> mTranslateNumpadMap; diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp index a4b7a65cb2..861fd10e30 100644 --- a/indra/llwindow/llwindow.cpp +++ b/indra/llwindow/llwindow.cpp @@ -418,7 +418,7 @@ LLWindow* LLWindowManager::createWindow( #if LL_WINDOWS new_window = new LLWindowWin32(callbacks, title, name, x, y, width, height, flags, - fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples, max_cores, max_vram, max_gl_version); + fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples, max_cores, max_gl_version); #elif LL_DARWIN new_window = new LLWindowMacOSX(callbacks, title, name, x, y, width, height, flags, diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index ebe027689a..2e520037b3 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -82,10 +82,10 @@ <key>AFKTimeout</key> <map> <key>Comment</key> - <string> - Time before automatically setting AFK (away from keyboard) mode (seconds, 0=never). - Valid values are: 0, 120, 300, 600, 1800 -</string> + <string> + Time before automatically setting AFK (away from keyboard) mode (seconds, 0=never). + Valid values are: 0, 120, 300, 600, 1800 + </string> <key>Persist</key> <integer>1</integer> <key>Type</key> @@ -379,7 +379,7 @@ <key>Value</key> <integer>0</integer> </map> - <key>AutoAcceptNewInventory</key> + <key>AutoAcceptNewInventory</key> <map> <key>Comment</key> <string>Automatically accept new notecards/textures/landmarks</string> @@ -2645,6 +2645,50 @@ <key>Value</key> <integer>0</integer> </map> + <key>AnalogChannelMappings</key> + <map> + <key>Comment</key> + <string>GameControl analog button to channel mappings</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>push:AXIS_1+,slide:AXIS_0+,jump:AXIS_4+,turn:AXIS_2+,look:AXIS_3+</string> + </map> + <key>BinaryChannelMappings</key> + <map> + <key>Comment</key> + <string>GameControl binary button to channel mappings</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>toggle_run:BUTTON_9,toggle_fly:BUTTON_11,toggle_flycam:BUTTON_10,stop:BUTTON_7</string> + </map> + <key>FlycamChannelMappings</key> + <map> + <key>Comment</key> + <string>GameControl flycam button to channel mappings</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>advance:AXIS_1+,pan:AXIS_0+,rise:AXIS_5+,pitch:AXIS_3-,yaw:AXIS_2+</string> + </map> + <key>KnownGameControllers</key> + <map> + <key>Comment</key> + <string>Map of controller device-specific options.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>LLSD</string> + <key>Value</key> + <map/> + </map> <key>EnableGestureSounds</key> <map> <key>Comment</key> diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index cb7606ac2e..dcd2a2f98d 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1135,10 +1135,13 @@ bool LLAppViewer::init() LLViewerJoystick::getInstance()->init(false); } - LLGameControl::init(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "gamecontrollerdb.txt")); - LLGameControl::enableSendToServer(gSavedSettings.getBOOL("GameControlToServer")); - LLGameControl::enableControlAgent(gSavedSettings.getBOOL("GameControlToAgent")); - LLGameControl::enableTranslateAgentActions(gSavedSettings.getBOOL("AgentToGameControl")); + LLGameControl::init(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "gamecontrollerdb.txt"), + [&](const std::string& name) -> bool { return gSavedSettings.getBOOL(name); }, + [&](const std::string& name, bool value) { gSavedSettings.setBOOL(name, value); }, + [&](const std::string& name) -> std::string { return gSavedSettings.getString(name); }, + [&](const std::string& name, const std::string& value) { gSavedSettings.setString(name, value); }, + [&](const std::string& name) -> LLSD { return gSavedSettings.getLLSD(name); }, + [&](const std::string& name, const LLSD& value) { gSavedSettings.setLLSD(name, value); }); try { @@ -1423,46 +1426,6 @@ bool LLAppViewer::frame() return ret; } -// util for detecting most active input channel -LLGameControl::InputChannel get_active_input_channel(const LLGameControl::State& state) -{ - LLGameControl::InputChannel input; - if (state.mButtons > 0) - { - // check buttons - input.mType = LLGameControl::InputChannel::TYPE_BUTTON; - for (U8 i = 0; i < 32; ++i) - { - if ((0x1 << i) & state.mButtons) - { - input.mIndex = i; - break; - } - } - } - else - { - // scan axes - S16 threshold = std::numeric_limits<S16>::max() / 2; - for (U8 i = 0; i < 6; ++i) - { - if (abs(state.mAxes[i]) > threshold) - { - input.mType = LLGameControl::InputChannel::TYPE_AXIS; - // input.mIndex ultimately translates to a LLGameControl::KeyboardAxis - // which distinguishes between negative and positive directions - // so we must translate to axis index "i" according to the sign - // of the axis value. - input.mIndex = i; - input.mSign = state.mAxes[i] > 0 ? 1 : -1; - break; - } - } - } - return input; -} - - void sendGameControlInput() { LLMessageSystem* msg = gMessageSystem; @@ -4860,13 +4823,9 @@ void LLAppViewer::idle() bool should_send_game_control = LLGameControl::computeFinalStateAndCheckForChanges(); if (LLPanelPreferenceGameControl::isWaitingForInputChannel()) { - LLGameControl::InputChannel channel = get_active_input_channel(LLGameControl::getState()); - if (channel.mType != LLGameControl::InputChannel::TYPE_NONE) - { - LLPanelPreferenceGameControl::applyGameControlInput(channel); - // skip this send because input is being used to set preferences - should_send_game_control = false; - } + LLPanelPreferenceGameControl::applyGameControlInput(); + // skip this send because input is being used to set preferences + should_send_game_control = false; } if (should_send_game_control) { diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 47777bf85c..2aa54eef57 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -563,21 +563,19 @@ void LLFloaterPreference::draw() void LLFloaterPreference::saveSettings() { LLTabContainer* tabcontainer = getChild<LLTabContainer>("pref core"); - child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - child_list_t::const_iterator end = tabcontainer->getChildList()->end(); - for ( ; iter != end; ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast<LLPanelPreference*>(view); - if (panel) + if (LLPanelPreference* panel = dynamic_cast<LLPanelPreference*>(view)) + { panel->saveSettings(); + } } saveIgnoredNotifications(); } void LLFloaterPreference::apply() { - LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this ); + LLAvatarPropertiesProcessor::getInstance()->addObserver(gAgent.getID(), this); LLTabContainer* tabcontainer = getChild<LLTabContainer>("pref core"); if (sSkin != gSavedSettings.getString("SkinCurrent")) @@ -585,14 +583,14 @@ void LLFloaterPreference::apply() LLNotificationsUtil::add("ChangeSkin"); refreshSkin(this); } + // Call apply() on all panels that derive from LLPanelPreference - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast<LLPanelPreference*>(view); - if (panel) + if (LLPanelPreference* panel = dynamic_cast<LLPanelPreference*>(view)) + { panel->apply(); + } } gViewerWindow->requestResolutionUpdate(); // for UIScaleFactor @@ -606,7 +604,7 @@ void LLFloaterPreference::apply() LLViewerMedia::getInstance()->setCookiesEnabled(getChild<LLUICtrl>("cookies_enabled")->getValue()); - if (hasChild("web_proxy_enabled", true) &&hasChild("web_proxy_editor", true) && hasChild("web_proxy_port", true)) + if (hasChild("web_proxy_enabled", true) && hasChild("web_proxy_editor", true) && hasChild("web_proxy_port", true)) { bool proxy_enable = getChild<LLUICtrl>("web_proxy_enabled")->getValue(); std::string proxy_address = getChild<LLUICtrl>("web_proxy_editor")->getValue(); @@ -643,13 +641,12 @@ void LLFloaterPreference::cancel(const std::vector<std::string> settings_to_skip { LLTabContainer* tabcontainer = getChild<LLTabContainer>("pref core"); // Call cancel() on all panels that derive from LLPanelPreference - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast<LLPanelPreference*>(view); - if (panel) - panel->cancel(settings_to_skip); + if (LLPanelPreference* panel = dynamic_cast<LLPanelPreference*>(view)) + { + panel->cancel(); + } } // hide joystick pref floater LLFloaterReg::hideInstance("pref_joystick"); @@ -678,7 +675,7 @@ void LLFloaterPreference::cancel(const std::vector<std::string> settings_to_skip } //Need to reload the navmesh if the pathing console is up LLHandle<LLFloaterPathfindingConsole> pathfindingConsoleHandle = LLFloaterPathfindingConsole::getInstanceHandle(); - if ( !pathfindingConsoleHandle.isDead() ) + if (!pathfindingConsoleHandle.isDead()) { LLFloaterPathfindingConsole* pPathfindingConsole = pathfindingConsoleHandle.get(); pPathfindingConsole->onRegionBoundaryCross(); @@ -695,6 +692,15 @@ void LLFloaterPreference::cancel(const std::vector<std::string> settings_to_skip void LLFloaterPreference::onOpen(const LLSD& key) { + LLTabContainer* tabcontainer = getChild<LLTabContainer>("pref core"); + for (LLView* view : *tabcontainer->getChildList()) + { + if (LLPanelPreference* panel = dynamic_cast<LLPanelPreference*>(view)) + { + panel->onOpen(key); + } + } + // this variable and if that follows it are used to properly handle do not disturb mode response message static bool initialized = false; // if user is logged in and we haven't initialized do not disturb mode response yet, do it @@ -762,8 +768,7 @@ void LLFloaterPreference::onOpen(const LLSD& key) // while preferences floater was closed. buildPopupLists(); - - //get the options that were checked + // get the options that were checked onNotificationsChange("FriendIMOptions"); onNotificationsChange("NonFriendIMOptions"); onNotificationsChange("ConferenceIMOptions"); @@ -775,8 +780,7 @@ void LLFloaterPreference::onOpen(const LLSD& key) refresh(); // Make sure the current state of prefs are saved away when - // when the floater is opened. That will make cancel do its - // job + // the floater is opened. That will make cancel() do its job saveSettings(); // Make sure there is a default preference file @@ -976,9 +980,9 @@ void LLFloaterPreference::onBtnOK(const LLSD& userdata) } //Conversation transcript and log path changed so reload conversations based on new location - if(mPriorInstantMessageLogPath.length()) + if (mPriorInstantMessageLogPath.length()) { - if(moveTranscriptsAndLog()) + if (moveTranscriptsAndLog()) { //When floaters are empty but have a chat history files, reload chat history into them LLFloaterIMSessionTab::reloadEmptyFloaters(); @@ -995,11 +999,13 @@ void LLFloaterPreference::onBtnOK(const LLSD& userdata) LLUIColorTable::instance().saveUserSettings(); gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), true); - //Only save once logged in and loaded per account settings - if(mGotPersonalInfo) + LLGameControl::loadFromSettings(); + + // Only save once logged in and loaded per account settings + if (mGotPersonalInfo) { gSavedPerAccountSettings.saveToFile(gSavedSettings.getString("PerAccountSettingsFile"), true); - } + } } else { @@ -1045,8 +1051,7 @@ void LLFloaterPreference::onBtnCancel(const LLSD& userdata) // static void LLFloaterPreference::updateUserInfo(const std::string& visibility) { - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences"); - if (instance) + if (LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences")) { instance->setPersonalInfo(visibility); } @@ -1054,14 +1059,12 @@ void LLFloaterPreference::updateUserInfo(const std::string& visibility) void LLFloaterPreference::refreshEnabledGraphics() { - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences"); - if (instance) + if (LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences")) { instance->refresh(); } - LLFloater* advanced = LLFloaterReg::findTypedInstance<LLFloater>("prefs_graphics_advanced"); - if (advanced) + if (LLFloater* advanced = LLFloaterReg::findTypedInstance<LLFloater>("prefs_graphics_advanced")) { advanced->refresh(); } @@ -1096,7 +1099,7 @@ void LLFloaterPreference::onNotificationsChange(const std::string& OptionName) bool show_notifications_alert = true; for (notifications_map::iterator it_notification = mNotificationOptions.begin(); it_notification != mNotificationOptions.end(); it_notification++) { - if(it_notification->second != "No action") + if (it_notification->second != "No action") { show_notifications_alert = false; break; @@ -1108,8 +1111,7 @@ void LLFloaterPreference::onNotificationsChange(const std::string& OptionName) void LLFloaterPreference::onNameTagOpacityChange(const LLSD& newvalue) { - LLColorSwatchCtrl* color_swatch = findChild<LLColorSwatchCtrl>("background"); - if (color_swatch) + if (LLColorSwatchCtrl* color_swatch = findChild<LLColorSwatchCtrl>("background")) { LLColor4 new_color = color_swatch->get(); color_swatch->set(new_color.setAlpha((F32)newvalue.asReal())); @@ -1290,7 +1292,7 @@ void LLAvatarComplexityControls::setIndirectMaxArc() void LLFloaterPreference::refresh() { - LLPanel::refresh(); + LLFloater::refresh(); setMaxNonImpostorsText( gSavedSettings.getU32("RenderAvatarMaxNonImpostors"), getChild<LLTextBox>("IndirectMaxNonImpostorsText", true)); @@ -1298,8 +1300,7 @@ void LLFloaterPreference::refresh() gSavedSettings.getU32("RenderAvatarMaxComplexity"), getChild<LLTextBox>("IndirectMaxComplexityText", true)); refreshEnabledState(); - LLFloater* advanced = LLFloaterReg::findTypedInstance<LLFloater>("prefs_graphics_advanced"); - if (advanced) + if (LLFloater* advanced = LLFloaterReg::findTypedInstance<LLFloater>("prefs_graphics_advanced")) { advanced->refresh(); } @@ -1313,7 +1314,7 @@ void LLFloaterPreference::onCommitWindowedMode() void LLFloaterPreference::onChangeQuality(const LLSD& data) { - U32 level = (U32)(data.asReal()); + U32 level = (U32)data.asReal(); LLFeatureManager::getInstance()->setGraphicsLevel(level, true); refreshEnabledGraphics(); refresh(); @@ -1393,7 +1394,6 @@ void LLFloaterPreference::onClickLogPath() std::string proposed_name(gSavedPerAccountSettings.getString("InstantMessageLogPath")); mPriorInstantMessageLogPath.clear(); - (new LLDirPickerThread(boost::bind(&LLFloaterPreference::changeLogPath, this, _1, _2), proposed_name))->getFile(); } @@ -1418,10 +1418,10 @@ bool LLFloaterPreference::moveTranscriptsAndLog() bool madeDirectory = false; //Does the directory really exist, if not then make it - if(!LLFile::isdir(chatLogPath)) + if (!LLFile::isdir(chatLogPath)) { //mkdir success is defined as zero - if(LLFile::mkdir(chatLogPath) != 0) + if (LLFile::mkdir(chatLogPath) != 0) { return false; } @@ -1431,10 +1431,10 @@ bool LLFloaterPreference::moveTranscriptsAndLog() std::string originalConversationLogDir = LLConversationLog::instance().getFileName(); std::string targetConversationLogDir = gDirUtilp->add(chatLogPath, "conversation.log"); //Try to move the conversation log - if(!LLConversationLog::instance().moveLog(originalConversationLogDir, targetConversationLogDir)) + if (!LLConversationLog::instance().moveLog(originalConversationLogDir, targetConversationLogDir)) { //Couldn't move the log and created a new directory so remove the new directory - if(madeDirectory) + if (madeDirectory) { LLFile::rmdir(chatLogPath); } @@ -1447,7 +1447,7 @@ bool LLFloaterPreference::moveTranscriptsAndLog() LLLogChat::getListOfTranscriptFiles(listOfTranscripts); - if(!LLLogChat::moveTranscripts(gDirUtilp->getChatLogsDir(), + if (!LLLogChat::moveTranscripts(gDirUtilp->getChatLogsDir(), instantMessageLogPath, listOfTranscripts, listOfFilesMoved)) @@ -1460,7 +1460,7 @@ bool LLFloaterPreference::moveTranscriptsAndLog() //Move the conversation log back LLConversationLog::instance().moveLog(targetConversationLogDir, originalConversationLogDir); - if(madeDirectory) + if (madeDirectory) { LLFile::rmdir(chatLogPath); } @@ -1507,7 +1507,6 @@ void LLFloaterPreference::setPersonalInfo(const std::string& visibility) getChild<LLUICtrl>("voice_call_friends_only_check")->setValue(gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly")); } - void LLFloaterPreference::refreshUI() { refresh(); @@ -1791,8 +1790,7 @@ void LLFloaterPreference::onClickActionChange() void LLFloaterPreference::onAtmosShaderChange() { - LLCheckBoxCtrl* ctrl_alm = getChild<LLCheckBoxCtrl>("UseLightShaders"); - if(ctrl_alm) + if (LLCheckBoxCtrl* ctrl_alm = getChild<LLCheckBoxCtrl>("UseLightShaders")) { //Deferred/SSAO/Shadows bool bumpshiny = LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump") && gSavedSettings.getBOOL("RenderObjectBump"); @@ -1853,12 +1851,9 @@ void LLFloaterPreference::updateClickActionControls() // In such case we won't need to do this 'dynamic_cast' nightmare. // updateTable() can also be avoided LLTabContainer* tabcontainer = getChild<LLTabContainer>("pref core"); - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreferenceControls* panel = dynamic_cast<LLPanelPreferenceControls*>(view); - if (panel) + if (LLPanelPreferenceControls* panel = dynamic_cast<LLPanelPreferenceControls*>(view)) { panel->setKeyBind("walk_to", EMouseClickType::CLICK_LEFT, @@ -1892,12 +1887,9 @@ void LLFloaterPreference::updateClickActionViews() // Todo: This is a very ugly way to get access to keybindings. // Reconsider possible options. LLTabContainer* tabcontainer = getChild<LLTabContainer>("pref core"); - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) + for (LLView* view : *tabcontainer->getChildList()) { - LLView* view = *iter; - LLPanelPreferenceControls* panel = dynamic_cast<LLPanelPreferenceControls*>(view); - if (panel) + if (LLPanelPreferenceControls* panel = dynamic_cast<LLPanelPreferenceControls*>(view)) { click_to_walk = panel->canKeyBindHandle("walk_to", EMouseClickType::CLICK_LEFT, @@ -1969,7 +1961,6 @@ void LLFloaterPreference::changed() // set 'enable' property for 'Delete transcripts...' button updateDeleteTranscriptsButton(); - } void LLFloaterPreference::saveGraphicsPreset(std::string& preset) @@ -2077,7 +2068,6 @@ bool LLPanelPreference::postBuild() if (hasChild("media_enabled", true)) { bool media_enabled = gSavedSettings.getBOOL("AudioStreamingMedia"); - getChild<LLCheckBoxCtrl>("media_enabled")->set(media_enabled); getChild<LLCheckBoxCtrl>("autoplay_enabled")->setEnabled(media_enabled); } @@ -2134,41 +2124,41 @@ LLPanelPreference::~LLPanelPreference() delete mBandWidthUpdater; } } + +// virtual void LLPanelPreference::apply() { // no-op } +// virtual void LLPanelPreference::saveSettings() { - LLFloater* advanced = LLFloaterReg::findTypedInstance<LLFloater>("prefs_graphics_advanced"); - // Save the value of all controls in the hierarchy mSavedValues.clear(); std::list<LLView*> view_stack; view_stack.push_back(this); - if (advanced) + // Search for 'Advanced' panel and add it if found + if (LLFloater* advanced = LLFloaterReg::findTypedInstance<LLFloater>("prefs_graphics_advanced")) { view_stack.push_back(advanced); } - while(!view_stack.empty()) + + while (!view_stack.empty()) { // Process view on top of the stack LLView* curview = view_stack.front(); view_stack.pop_front(); - LLColorSwatchCtrl* color_swatch = dynamic_cast<LLColorSwatchCtrl *>(curview); - if (color_swatch) + if (LLColorSwatchCtrl* color_swatch = dynamic_cast<LLColorSwatchCtrl*>(curview)) { mSavedColors[color_swatch->getName()] = color_swatch->get(); } else { - LLUICtrl* ctrl = dynamic_cast<LLUICtrl*>(curview); - if (ctrl) + if (LLUICtrl* ctrl = dynamic_cast<LLUICtrl*>(curview)) { - LLControlVariable* control = ctrl->getControlVariable(); - if (control) + if (LLControlVariable* control = ctrl->getControlVariable()) { mSavedValues[control] = control->getValue(); } @@ -2176,10 +2166,9 @@ void LLPanelPreference::saveSettings() } // Push children onto the end of the work stack - for (child_list_t::const_iterator iter = curview->getChildList()->begin(); - iter != curview->getChildList()->end(); ++iter) + for (LLView* view : *curview->getChildList()) { - view_stack.push_back(*iter); + view_stack.push_back(view); } } @@ -2240,12 +2229,12 @@ void LLPanelPreference::cancel(const std::vector<std::string> settings_to_skip) { for (control_values_map_t::iterator iter = mSavedValues.begin(); iter != mSavedValues.end(); ++iter) - { +{ LLControlVariable* control = iter->first; LLSD ctrl_value = iter->second; if((control->getName() == "InstantMessageLogPath") && (ctrl_value.asString() == "")) - { + { continue; } @@ -2336,7 +2325,7 @@ public: if (find(mAccountIndependentSettings.begin(), mAccountIndependentSettings.end(), setting) == mAccountIndependentSettings.end()) { - mSavedValues.erase(it++); + it = mSavedValues.erase(it); } else { @@ -2500,9 +2489,11 @@ void LLPanelPreferenceGraphics::cancel(const std::vector<std::string> settings_t { LLPanelPreference::cancel(settings_to_skip); } + void LLPanelPreferenceGraphics::saveSettings() { resetDirtyChilds(); + std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); if (preset_graphic_active.empty()) { @@ -2513,8 +2504,10 @@ void LLPanelPreferenceGraphics::saveSettings() instance->saveGraphicsPreset(preset_graphic_active); } } + LLPanelPreference::saveSettings(); } + void LLPanelPreferenceGraphics::setHardwareDefaults() { resetDirtyChilds(); @@ -2610,12 +2603,9 @@ bool LLPanelPreferenceControls::addControlTableRows(const std::string &filename) cell_params.column = ""; cell_params.value = ""; - - for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = contents.rows.begin(); - row_it != contents.rows.end(); - ++row_it) + for (LLScrollListItem::Params& row_params : contents.rows) { - std::string control = row_it->value.getValue().asString(); + std::string control = row_params.value.getValue().asString(); if (!control.empty() && control != "menu_separator") { bool show = true; @@ -2634,7 +2624,7 @@ bool LLPanelPreferenceControls::addControlTableRows(const std::string &filename) if (show) { // At the moment viewer is hardcoded to assume that columns are named as lst_ctrl%d - LLScrollListItem::Params item_params(*row_it); + LLScrollListItem::Params item_params(row_params); item_params.enabled.setValue(enabled); S32 num_columns = pControlsTable->getNumColumns(); @@ -2659,7 +2649,7 @@ bool LLPanelPreferenceControls::addControlTableRows(const std::string &filename) // value = "menu_separator" // column = "lst_action" / > //</rows> - pControlsTable->addRow(*row_it, EAddPosition::ADD_BOTTOM); + pControlsTable->addRow(row_params, EAddPosition::ADD_BOTTOM); } } return true; @@ -3115,6 +3105,7 @@ void LLPanelPreferenceControls::onDefaultKeyBind(bool all_modes) mConflictHandler[mEditingMode].saveToSettings(true); } } + updateTable(); if (mEditingMode == LLKeyConflictHandler::MODE_THIRD_PERSON || all_modes) @@ -3136,7 +3127,10 @@ void LLPanelPreferenceControls::onCancelKeyBind() //------------------------LLPanelPreferenceGameControl-------------------------------- // LLPanelPreferenceGameControl is effectively a singleton, so we track its instance -static LLPanelPreferenceGameControl* gGameControlPanel; +static LLPanelPreferenceGameControl* gGameControlPanel { nullptr }; +static LLScrollListCtrl* gSelectedGrid { nullptr }; +static LLScrollListItem* gSelectedItem { nullptr }; +static LLScrollListCell* gSelectedCell { nullptr }; LLPanelPreferenceGameControl::LLPanelPreferenceGameControl() { @@ -3150,332 +3144,724 @@ LLPanelPreferenceGameControl::~LLPanelPreferenceGameControl() static LLPanelInjector<LLPanelPreferenceGameControl> t_pref_game_control("panel_preference_game_control"); -void LLPanelPreferenceGameControl::apply() -{ -} - -void LLPanelPreferenceGameControl::loadDefaults() -{ - // TODO: implement this -} - -void LLPanelPreferenceGameControl::loadSettings() -{ - // TODO: implement this -} - +// Collect all UI control values into mSavedValues void LLPanelPreferenceGameControl::saveSettings() { - // TODO: implement this -} - -void LLPanelPreferenceGameControl::updateEnabledState() -{ - // TODO?: implement this -} + LLPanelPreference::saveSettings(); -static LLScrollListItem* gSelectedItem { nullptr }; -static LLScrollListCell* gSelectedCell { nullptr }; + std::vector<LLScrollListItem*> items = mActionTable->getAllData(); -void LLPanelPreferenceGameControl::onClickGameControlToServer(LLUICtrl* ctrl) -{ - BOOL checked = mCheckGameControlToServer->get(); - gSavedSettings.setBOOL( "GameControlToServer", checked ); - LLGameControl::enableSendToServer(checked); -} + // Find the channel visually associated with the specified action + LLGameControl::getChannel_t getChannel = + [&](const std::string& action) -> LLGameControl::InputChannel + { + for (LLScrollListItem* item : items) + { + if (action == item->getValue() && (item->getNumColumns() >= 2)) + { + return LLGameControl::getChannelByName(item->getColumn(1)->getValue()); + } + } + return LLGameControl::InputChannel(); + }; -void LLPanelPreferenceGameControl::onClickGameControlToAgent(LLUICtrl* ctrl) -{ - BOOL checked = mCheckGameControlToAgent->get(); - gSavedSettings.setBOOL( "GameControlToAgent", checked ); - LLGameControl::enableControlAgent(checked); + // Use string formatting functions provided by class LLGameControl: + // stringifyAnalogMappings(), stringifyBinaryMappings(), stringifyFlycamMappings() - mActionTable->deselectAllItems(); - bool table_enabled = checked || mCheckAgentToGameControl->get(); - mActionTable->setEnabled(table_enabled); - mChannelSelector->setEnabled(table_enabled); - LLGameControl::enableTranslateAgentActions(checked); -} + if (LLControlVariable* analogMappings = gSavedSettings.getControl("AnalogChannelMappings")) + { + analogMappings->set(LLGameControl::stringifyAnalogMappings(getChannel)); + mSavedValues[analogMappings] = analogMappings->getValue(); + } -void LLPanelPreferenceGameControl::onClickAgentToGameControl(LLUICtrl* ctrl) -{ - BOOL checked = mCheckAgentToGameControl->get(); - gSavedSettings.setBOOL( "AgentToGameControl", checked ); + if (LLControlVariable* binaryMappings = gSavedSettings.getControl("BinaryChannelMappings")) + { + binaryMappings->set(LLGameControl::stringifyBinaryMappings(getChannel)); + mSavedValues[binaryMappings] = binaryMappings->getValue(); + } - mActionTable->deselectAllItems(); - bool table_enabled = checked || mCheckGameControlToAgent->get(); - mActionTable->setEnabled(table_enabled); - mChannelSelector->setEnabled(table_enabled); - LLGameControl::enableTranslateAgentActions(checked); + if (LLControlVariable* flycamMappings = gSavedSettings.getControl("FlycamChannelMappings")) + { + flycamMappings->set(LLGameControl::stringifyFlycamMappings(getChannel)); + mSavedValues[flycamMappings] = flycamMappings->getValue(); + } + if (LLControlVariable* knownControllers = gSavedSettings.getControl("KnownGameControllers")) + { + LLSD deviceOptions(LLSD::emptyMap()); + for (auto& pair : mDeviceOptions) + { + pair.second.settings = pair.second.options.saveToString(pair.second.name); + if (!pair.second.settings.empty()) + { + deviceOptions.insert(pair.first, pair.second.settings); + } + } + knownControllers->set(deviceOptions); + mSavedValues[knownControllers] = deviceOptions; + } } -void LLPanelPreferenceGameControl::onActionSelect() +void LLPanelPreferenceGameControl::onGridSelect(LLUICtrl* ctrl) { clearSelectionState(); - LLScrollListItem* item = mActionTable->getFirstSelected(); - if (item == NULL) - { + LLScrollListCtrl* table = dynamic_cast<LLScrollListCtrl*>(ctrl); + if (!table || !table->getEnabled()) return; + + if (LLScrollListItem* item = table->getFirstSelected()) + { + if (initCombobox(item, table)) + return; + + table->deselectAllItems(); } +} - std::string action = item->getValue(); +bool LLPanelPreferenceGameControl::initCombobox(LLScrollListItem* item, LLScrollListCtrl* grid) +{ + if (item->getSelectedCell() != 1) + return false; + + LLScrollListText* cell = dynamic_cast<LLScrollListText*>(item->getColumn(1)); + if (!cell) + return false; - if (action.empty()) + LLComboBox* combobox = nullptr; + if (grid == mActionTable) { - mActionTable->deselectAllItems(); - return; + std::string action = item->getValue(); + LLGameControl::ActionNameType actionNameType = LLGameControl::getActionNameType(action); + combobox = + actionNameType == LLGameControl::ACTION_NAME_ANALOG ? mAnalogChannelSelector : + actionNameType == LLGameControl::ACTION_NAME_BINARY ? mBinaryChannelSelector : + actionNameType == LLGameControl::ACTION_NAME_FLYCAM ? mAnalogChannelSelector : + nullptr; } - - S32 cell_index = item->getSelectedCell(); - if (cell_index != 1) + else if (grid == mAxisMappings) { - mActionTable->deselectAllItems(); - return; + combobox = mAxisSelector; } - - LLScrollListText* cell = dynamic_cast<LLScrollListText*>(item->getColumn(cell_index)); - if (cell) + else if (grid == mButtonMappings) { - gSelectedItem = item; - gSelectedCell = cell; + combobox = mBinaryChannelSelector; + } + if (!combobox) + return false; - // compute new rect for mChannelSelector - S32 row = mActionTable->getFirstSelectedIndex(); - S32 column = item->getSelectedCell(); - LLRect cell_rect = mActionTable->getCellRect(row, column); + // compute new rect for combobox + S32 row_index = grid->getItemIndex(item); + fitInRect(combobox, grid, row_index, 1); - LLRect combo_rect = mChannelSelector->getRect(); - S32 width = combo_rect.getWidth(); - S32 height = combo_rect.getHeight(); - S32 left = cell_rect.mLeft + cell->getTextWidth(); - combo_rect.set(left, cell_rect.mTop, left + width, cell_rect.mTop - height); - mChannelSelector->setRect(combo_rect); + std::string channel_name = "NONE"; + std::string cell_value = cell->getValue(); + std::vector<LLScrollListItem*> items = combobox->getAllData(); + for (const LLScrollListItem* item : items) + { + if (item->getColumn(0)->getValue().asString() == cell_value) + { + channel_name = item->getValue().asString(); + break; + } + } - std::string value = gSelectedCell->getValue(); - if (value == " ") + std::string value; + LLGameControl::InputChannel channel = LLGameControl::getChannelByName(channel_name); + if (!channel.isNone()) + { + std::string channel_name = channel.getLocalName(); + std::string channel_label = getChannelLabel(channel_name, combobox->getAllData()); + if (combobox->itemExists(channel_label)) { - value = "NONE"; + value = channel_name; } - mChannelSelector->setValue(value); - mChannelSelector->setVisible(TRUE); - mChannelSelector->showList(); } - else + if (value.empty()) { - mActionTable->deselectAllItems(); + // Assign the last element in the dropdown list which is "NONE" + value = combobox->getAllData().back()->getValue().asString(); } + + combobox->setValue(value); + combobox->setVisible(TRUE); + combobox->showList(); + + gSelectedGrid = grid; + gSelectedItem = item; + gSelectedCell = cell; + + return true; } -void LLPanelPreferenceGameControl::onCommitInputChannel() +void LLPanelPreferenceGameControl::onCommitInputChannel(LLUICtrl* ctrl) { - if (gSelectedCell) - { - std::string channel_name = mChannelSelector->getSelectedItemLabel(); - LLGameControl::InputChannel channel = LLGameControl::getChannelByName(channel_name); + if (!gSelectedGrid || !gSelectedItem || !gSelectedCell) + return; + + LLComboBox* combobox = dynamic_cast<LLComboBox*>(ctrl); + llassert(combobox); + if (!combobox) + return; - std::string action_name = gSelectedItem->getValue(); - bool success = LLGameControl::updateActionMap(action_name, channel); - if (success) + if (gSelectedGrid == mActionTable) + { + std::string value = combobox->getValue(); + std::string label = (value == "NONE") ? + LLStringUtil::null : combobox->getSelectedItemLabel(); + gSelectedCell->setValue(label); + } + else + { + S32 chosen_index = combobox->getCurrentIndex(); + if (chosen_index >= 0) { - if (channel_name == "NONE") + int row_index = gSelectedGrid->getItemIndex(gSelectedItem); + llassert(row_index >= 0); + LLGameControl::Options& deviceOptions = getSelectedDeviceOptions(); + std::vector<U8>& map = gSelectedGrid == mAxisMappings ? + deviceOptions.getAxisMap() : deviceOptions.getButtonMap(); + if (chosen_index >= map.size()) { - gSelectedCell->setValue(" "); - // TODO?: also clear cell to the right with script-relevant name - } - else - { - gSelectedCell->setValue(channel_name); - // TODO?: also update the cell to the right with script-relevant name + chosen_index = row_index; } + std::string label = chosen_index == row_index ? + LLStringUtil::null : combobox->getSelectedItemLabel(); + gSelectedCell->setValue(label); + map[row_index] = chosen_index; } - gGameControlPanel->updateTable(); - clearSelectionState(); } + gSelectedGrid->deselectAllItems(); + clearSelectionState(); } bool LLPanelPreferenceGameControl::isWaitingForInputChannel() { - return gSelectedItem != nullptr; + return gSelectedCell != nullptr; } // static -void LLPanelPreferenceGameControl::applyGameControlInput(const LLGameControl::InputChannel& channel) +void LLPanelPreferenceGameControl::applyGameControlInput() +{ + if (!gGameControlPanel || !gSelectedGrid || !gSelectedCell) + return; + + LLComboBox* combobox; + LLGameControl::InputChannel::Type expectedType; + if (gGameControlPanel->mAnalogChannelSelector->getVisible()) + { + combobox = gGameControlPanel->mAnalogChannelSelector; + expectedType = LLGameControl::InputChannel::TYPE_AXIS; + } + else if (gGameControlPanel->mBinaryChannelSelector->getVisible()) + { + combobox = gGameControlPanel->mBinaryChannelSelector; + expectedType = LLGameControl::InputChannel::TYPE_BUTTON; + } + else + { + return; + } + + LLGameControl::InputChannel channel = LLGameControl::getActiveInputChannel(); + if (channel.mType == expectedType) + { + std::string channel_name = channel.getLocalName(); + std::string channel_label = LLPanelPreferenceGameControl::getChannelLabel(channel_name, combobox->getAllData()); + gSelectedCell->setValue(channel_label); + gSelectedGrid->deselectAllItems(); + gGameControlPanel->clearSelectionState(); + } +} + +void LLPanelPreferenceGameControl::onAxisOptionsSelect() { - if (gSelectedItem && channel.mType != (U8)(LLPanelPreferenceGameControl::TYPE_NONE)) + clearSelectionState(); + + if (LLScrollListItem* row = mAxisOptions->getFirstSelected()) { - S32 cell_index = gSelectedItem->getSelectedCell(); - if (cell_index > 0) + LLGameControl::Options& deviceOptions = getSelectedDeviceOptions(); + S32 row_index = mAxisOptions->getItemIndex(row); + S32 column_index = row->getSelectedCell(); + if (column_index == 1) { - LLScrollListCell* cell = gSelectedItem->getColumn(cell_index); - if (cell) + LLGameControl::Options& deviceOptions = getSelectedDeviceOptions(); + deviceOptions.getAxisOptions()[row_index].mInvert = + row->getColumn(column_index)->getValue().asBoolean(); + } + else if (column_index == 2 || column_index == 3) + { + fitInRect(mNumericValueEditor, mAxisOptions, row_index, column_index); + if (column_index == 2) { - bool success = LLGameControl::updateActionMap(gSelectedItem->getValue(), channel); - if (success) - { - cell->setValue(channel.getLocalName()); - // TODO?: also update the cell to the right with script-relevant name - gGameControlPanel->updateTable(); - } - + mNumericValueEditor->setMinValue(0); + mNumericValueEditor->setMaxValue(LLGameControl::MAX_AXIS_DEAD_ZONE); + mNumericValueEditor->setValue(deviceOptions.getAxisOptions()[row_index].mDeadZone); } + else // column_index == 3 + { + mNumericValueEditor->setMinValue(-LLGameControl::MAX_AXIS_OFFSET); + mNumericValueEditor->setMaxValue(LLGameControl::MAX_AXIS_OFFSET); + mNumericValueEditor->setValue(deviceOptions.getAxisOptions()[row_index].mOffset); + } + mNumericValueEditor->setVisible(TRUE); } - gGameControlPanel->clearSelectionState(); + + initCombobox(row, mAxisOptions); + } +} + +void LLPanelPreferenceGameControl::onCommitNumericValue() +{ + if (LLScrollListItem* row = mAxisOptions->getFirstSelected()) + { + LLGameControl::Options& deviceOptions = getSelectedDeviceOptions(); + S32 value = mNumericValueEditor->getValue().asInteger(); + S32 row_index = mAxisOptions->getItemIndex(row); + S32 column_index = row->getSelectedCell(); + llassert(column_index == 2 || column_index == 3); + if (column_index != 2 && column_index != 3) + return; + + if (column_index == 2) + { + value = std::clamp<S32>(value, 0, LLGameControl::MAX_AXIS_DEAD_ZONE); + deviceOptions.getAxisOptions()[row_index].mDeadZone = (U16)value; + } + else // column_index == 3 + { + value = std::clamp<S32>(value, -LLGameControl::MAX_AXIS_OFFSET, LLGameControl::MAX_AXIS_OFFSET); + deviceOptions.getAxisOptions()[row_index].mOffset = (S16)value; + } + setNumericLabel(row->getColumn(column_index), value); } } BOOL LLPanelPreferenceGameControl::postBuild() { + // Above the tab container mCheckGameControlToServer = getChild<LLCheckBoxCtrl>("game_control_to_server"); - mCheckGameControlToServer->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onClickGameControlToServer, this, _1)); - //mCheckGameControlToServer->setEnabled(gSavedSettings.getBOOL( "GameControlToServer")); - mCheckGameControlToAgent = getChild<LLCheckBoxCtrl>("game_control_to_agent"); - mCheckGameControlToAgent->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onClickGameControlToAgent, this, _1)); - //mCheckGameControlToAgent->setEnabled(gSavedSettings.getBOOL( "GameControlToAgent")); + mCheckAgentToGameControl = getChild<LLCheckBoxCtrl>("agent_to_game_control"); + + mCheckGameControlToAgent->setCommitCallback([this](LLUICtrl*, const LLSD&) { updateActionTableState(); }); + mCheckAgentToGameControl->setCommitCallback([this](LLUICtrl*, const LLSD&) { updateActionTableState(); }); - mCheckAgentToGameControl= getChild<LLCheckBoxCtrl>("agent_to_game_control"); - mCheckAgentToGameControl->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onClickAgentToGameControl, this, _1)); - //mCheckAgentToGameControl->setEnabled(gSavedSettings.getBOOL( "AgentToGameControl")); + getChild<LLTabContainer>("game_control_tabs")->setCommitCallback([this](LLUICtrl*, const LLSD&) { clearSelectionState(); }); + getChild<LLTabContainer>("device_settings_tabs")->setCommitCallback([this](LLUICtrl*, const LLSD&) { clearSelectionState(); }); + // 1st tab "Channel mappings" + mTabChannelMappings = getChild<LLPanel>("tab_channel_mappings"); mActionTable = getChild<LLScrollListCtrl>("action_table"); - mActionTable->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onActionSelect, this)); + mActionTable->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onGridSelect(ctrl); }); + + // 2nd tab "Device settings" + mTabDeviceSettings = getChild<LLPanel>("tab_device_settings"); + mNoDeviceMessage = getChild<LLTextBox>("nodevice_message"); + mDevicePrompt = getChild<LLTextBox>("device_prompt"); + mSingleDevice = getChild<LLTextBox>("single_device"); + mDeviceList = getChild<LLComboBox>("device_list"); + mCheckShowAllDevices = getChild<LLCheckBoxCtrl>("show_all_known_devices"); + mPanelDeviceSettings = getChild<LLPanel>("device_settings"); + + mCheckShowAllDevices->setCommitCallback([this](LLUICtrl*, const LLSD&) { populateDeviceTitle(); }); + mDeviceList->setCommitCallback([this](LLUICtrl*, const LLSD& value) { populateDeviceSettings(value); }); + + mTabAxisOptions = getChild<LLPanel>("tab_axis_options"); + mAxisOptions = getChild<LLScrollListCtrl>("axis_options"); + mAxisOptions->setCommitCallback([this](LLUICtrl*, const LLSD&) { onAxisOptionsSelect(); }); + + mTabAxisMappings = getChild<LLPanel>("tab_axis_mappings"); + mAxisMappings = getChild<LLScrollListCtrl>("axis_mappings"); + mAxisMappings->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onGridSelect(ctrl); }); + + mTabButtonMappings = getChild<LLPanel>("tab_button_mappings"); + mButtonMappings = getChild<LLScrollListCtrl>("button_mappings"); + mButtonMappings->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onGridSelect(ctrl); }); + + mResetToDefaults = getChild<LLButton>("reset_to_defaults"); + mResetToDefaults->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onResetToDefaults(); }); + + // Numeric value editor + mNumericValueEditor = getChild<LLSpinCtrl>("numeric_value_editor"); + mNumericValueEditor->setCommitCallback([this](LLUICtrl*, const LLSD&) { onCommitNumericValue(); }); + + // Channel selectors + mAnalogChannelSelector = getChild<LLComboBox>("analog_channel_selector"); + mAnalogChannelSelector->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onCommitInputChannel(ctrl); }); + + mBinaryChannelSelector = getChild<LLComboBox>("binary_channel_selector"); + mBinaryChannelSelector->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onCommitInputChannel(ctrl); }); + + mAxisSelector = getChild<LLComboBox>("axis_selector"); + mAxisSelector->setCommitCallback([this](LLUICtrl* ctrl, const LLSD&) { onCommitInputChannel(ctrl); }); + + // Setup the 1st tab + populateActionTableRows("game_control_table_rows.xml"); + addActionTableSeparator(); + populateActionTableRows("game_control_table_camera_rows.xml"); + + // Setup the 2nd tab + populateOptionsTableRows(); + populateMappingTableRows(mAxisMappings, mAxisSelector, LLGameControl::NUM_AXES); + populateMappingTableRows(mButtonMappings, mBinaryChannelSelector, LLGameControl::NUM_BUTTONS); + + // Workaround for the common bug: + // LLScrollListCtrl with draw_heading="true" initially has incorrect mTop (17 px higher) + LLRect rect = mAxisOptions->getRect(); + rect.mTop = mAxisOptions->getParent()->getRect().getHeight() - 1; + mAxisOptions->setRect(rect); + mAxisOptions->updateLayout(); - populateActionTable(); + return TRUE; +} - // enable the table if at least one of the GameControl<-->Avatar options is enabled - mActionTable->setEnabled(mCheckGameControlToAgent->get() || mCheckAgentToGameControl->get()); +// Update all UI control values from real objects +// This function is called before floater is shown +void LLPanelPreferenceGameControl::onOpen(const LLSD& key) +{ + mCheckGameControlToServer->setValue(LLGameControl::getSendToServer()); + mCheckGameControlToAgent->setValue(LLGameControl::getControlAgent()); + mCheckAgentToGameControl->setValue(LLGameControl::getTranslateAgentActions()); - mChannelSelector = getChild<LLComboBox>("input_channel_combo"); - mChannelSelector->setVisible(FALSE); - mChannelSelector->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onCommitInputChannel, this)); + clearSelectionState(); - return TRUE; + // Setup the 1st tab + populateActionTableCells(); + updateActionTableState(); + + // Setup the 2nd tab + mDeviceOptions.clear(); + for (const auto& pair : LLGameControl::getDeviceOptions()) + { + DeviceOptions deviceOptions = { LLStringUtil::null, pair.second, LLGameControl::Options() }; + deviceOptions.options.loadFromString(deviceOptions.name, deviceOptions.settings); + mDeviceOptions.emplace(pair.first, deviceOptions); + } + // Add missing device settings/options even if they are default + for (const auto& device : LLGameControl::getDevices()) + { + if (mDeviceOptions.find(device.getGUID()) == mDeviceOptions.end()) + { + mDeviceOptions[device.getGUID()] = { device.getName(), device.saveOptionsToString(true), device.getOptions() }; + } + } + + mCheckShowAllDevices->setValue(false); + populateDeviceTitle(); +} + +void LLPanelPreferenceGameControl::populateActionTableRows(const std::string& filename) +{ + LLScrollListCtrl::Contents contents; + if (!parseXmlFile(contents, filename, "rows")) + return; + + // init basic cell params + LLScrollListCell::Params second_cell_params; + second_cell_params.font = LLFontGL::getFontSansSerif(); + second_cell_params.font_halign = LLFontGL::LEFT; + second_cell_params.column = mActionTable->getColumn(1)->mName; + second_cell_params.value = ""; // Actual value is assigned in populateActionTableCells + + for (const LLScrollListItem::Params& row_params : contents.rows) + { + std::string name = row_params.value.getValue().asString(); + if (!name.empty() && name != "menu_separator") + { + LLScrollListItem::Params new_params(row_params); + new_params.enabled.setValue(true); + // item_params should already have one column that was defined + // in XUI config file, and now we want to add one more + if (new_params.columns.size() == 1) + { + new_params.columns.add(second_cell_params); + } + mActionTable->addRow(new_params, EAddPosition::ADD_BOTTOM); + } + else + { + mActionTable->addRow(row_params, EAddPosition::ADD_BOTTOM); + } + } } -void LLPanelPreferenceGameControl::populateActionTable() +void LLPanelPreferenceGameControl::populateActionTableCells() { - loadSettings(); - populateColumns(); - populateRows("game_control_table_rows.xml"); - addTableSeparator(); - populateRows("game_control_table_camera_rows.xml"); + std::vector<LLScrollListItem*> rows = mActionTable->getAllData(); + std::vector<LLScrollListItem*> axes = mAnalogChannelSelector->getAllData(); + std::vector<LLScrollListItem*> btns = mBinaryChannelSelector->getAllData(); + + for (LLScrollListItem* row : rows) + { + if (row->getNumColumns() >= 2) // Skip separators + { + std::string name = row->getValue().asString(); + if (!name.empty() && name != "menu_separator") + { + LLGameControl::InputChannel channel = LLGameControl::getChannelByAction(name); + std::string channel_name = channel.getLocalName(); + std::string channel_label = + channel.isAxis() ? getChannelLabel(channel_name, axes) : + channel.isButton() ? getChannelLabel(channel_name, btns) : + LLStringUtil::null; + row->getColumn(1)->setValue(channel_label); + } + } + } } -void LLPanelPreferenceGameControl::populateColumns() +// static +bool LLPanelPreferenceGameControl::parseXmlFile(LLScrollListCtrl::Contents& contents, + const std::string& filename, const std::string& what) { - // populate columns - std::string filename = "game_control_table_columns.xml"; LLXMLNodePtr xmlNode; - LLScrollListCtrl::Contents contents; if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) { - LL_WARNS("Preferences") << "Failed to populate columns from '" << filename << "'" << LL_ENDL; - return; + LL_WARNS("Preferences") << "Failed to populate " << what << " from '" << filename << "'" << LL_ENDL; + return false; } + LLXUIParser parser; parser.readXUI(xmlNode, contents, filename); if (!contents.validateBlock()) { - LL_WARNS("Preferences") << "Failed to parse columns from '" << filename << "'" << LL_ENDL; - return; + LL_WARNS("Preferences") << "Failed to parse " << what << " from '" << filename << "'" << LL_ENDL; + return false; } - for (LLInitParam::ParamIterator<LLScrollListColumn::Params>::const_iterator col_it = contents.columns.begin(); - col_it != contents.columns.end(); - ++col_it) + + return true; +} + +void LLPanelPreferenceGameControl::populateDeviceTitle() +{ + mSelectedDeviceGUID.clear(); + + bool showAllDevices = mCheckShowAllDevices->getValue().asBoolean(); + std::size_t deviceCount = showAllDevices ? mDeviceOptions.size() : LLGameControl::getDevices().size(); + + mNoDeviceMessage->setVisible(!deviceCount); + mDevicePrompt->setVisible(deviceCount); + mSingleDevice->setVisible(deviceCount == 1); + mDeviceList->setVisible(deviceCount > 1); + mPanelDeviceSettings->setVisible(deviceCount); + + auto makeTitle = [](const std::string& guid, const std::string& name) -> std::string + { + return guid + ", " + name; + }; + + if (deviceCount == 1) { - mActionTable->addColumn(*col_it); + if (showAllDevices) + { + const std::pair<std::string, DeviceOptions>& pair = *mDeviceOptions.begin(); + mSingleDevice->setValue(makeTitle(pair.first, pair.second.name)); + populateDeviceSettings(pair.first); + } + else + { + const LLGameControl::Device& device = LLGameControl::getDevices().front(); + mSingleDevice->setValue(makeTitle(device.getGUID(), device.getName())); + populateDeviceSettings(device.getGUID()); + } + } + else if (deviceCount) + { + mDeviceList->clear(); + mDeviceList->clearRows(); + + auto makeListItem = [](const std::string& guid, const std::string& title) + { + return LLSD().with("value", guid).with("columns", LLSD().with("label", title)); + }; + + if (showAllDevices) + { + for (const auto& pair : mDeviceOptions) + { + mDeviceList->addElement(makeListItem(pair.first, makeTitle(pair.first, pair.second.name))); + } + } + else + { + for (const LLGameControl::Device& device : LLGameControl::getDevices()) + { + mDeviceList->addElement(makeListItem(device.getGUID(), makeTitle(device.getGUID(), device.getName()))); + } + } + + mDeviceList->selectNthItem(0); + populateDeviceSettings(mDeviceList->getValue()); } } -void LLPanelPreferenceGameControl::populateRows(const std::string& filename) +void LLPanelPreferenceGameControl::populateDeviceSettings(const std::string& guid) { - LLXMLNodePtr xmlNode; - if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) + LL_INFOS() << "guid: '" << guid << "'" << LL_ENDL; + + mSelectedDeviceGUID = guid; + auto options_it = mDeviceOptions.find(guid); + llassert_always(options_it != mDeviceOptions.end()); + const DeviceOptions& deviceOptions = options_it->second; + + populateOptionsTableCells(); + populateMappingTableCells(mAxisMappings, deviceOptions.options.getAxisMap(), mAxisSelector); + populateMappingTableCells(mButtonMappings, deviceOptions.options.getButtonMap(), mBinaryChannelSelector); +} + +void LLPanelPreferenceGameControl::populateOptionsTableRows() +{ + mAxisOptions->clearRows(); + + std::vector<LLScrollListItem*> items = mAnalogChannelSelector->getAllData(); + + LLScrollListItem::Params row_params; + LLScrollListCell::Params cell_params; + cell_params.font = LLFontGL::getFontMonospace(); + for (size_t i = 0; i < mAxisOptions->getNumColumns(); ++i) { - LL_WARNS("Preferences") << "Failed to populate rows from '" << filename << "'" << LL_ENDL; - return; + row_params.columns.add(cell_params); } - LLScrollListCtrl::Contents contents; - LLXUIParser parser; - parser.readXUI(xmlNode, contents, filename); - if (!contents.validateBlock()) + + row_params.columns(1).type = "checkbox"; + row_params.columns(2).font_halign = "right"; + row_params.columns(3).font_halign = "right"; + + for (size_t i = 0; i < LLGameControl::NUM_AXES; ++i) { - LL_WARNS("Preferences") << "Failed to parse rows from '" << filename << "'" << LL_ENDL; - return; + LLScrollListItem* row = mAxisOptions->addRow(row_params); + row->getColumn(0)->setValue(items[i]->getColumn(0)->getValue()); } +} - // init basic cell params +void LLPanelPreferenceGameControl::populateOptionsTableCells() +{ + std::vector<LLScrollListItem*> rows = mAxisOptions->getAllData(); + const auto& all_axis_options = getSelectedDeviceOptions().getAxisOptions(); + llassert(rows.size() == all_axis_options.size()); + + for (size_t i = 0; i < rows.size(); ++i) + { + LLScrollListItem* row = rows[i]; + const LLGameControl::Options::AxisOptions& axis_options = all_axis_options[i]; + row->getColumn(1)->setValue(axis_options.mInvert); + setNumericLabel(row->getColumn(2), axis_options.mDeadZone); + setNumericLabel(row->getColumn(3), axis_options.mOffset); + } +} + +void LLPanelPreferenceGameControl::populateMappingTableRows(LLScrollListCtrl* target, + const LLComboBox* source, size_t row_count) +{ + target->clearRows(); + + std::vector<LLScrollListItem*> items = source->getAllData(); + + LLScrollListItem::Params row_params; LLScrollListCell::Params cell_params; - cell_params.font = LLFontGL::getFontSansSerif(); - cell_params.font_halign = LLFontGL::LEFT; - cell_params.column = ""; - cell_params.value = ""; + cell_params.font = LLFontGL::getFontMonospace(); + for (size_t i = 0; i < target->getNumColumns(); ++i) + { + row_params.columns.add(cell_params); + } - // we expect the mActionTable to have at least three columns - if (mActionTable->getNumColumns() < 3) + for (size_t i = 0; i < row_count; ++i) { - LL_WARNS("Preferences") << "expected at least three columns in '" << filename << "'" << LL_ENDL; - return; + LLScrollListItem* row = target->addRow(row_params); + row->getColumn(0)->setValue(items[i]->getColumn(0)->getValue()); } - LLScrollListColumn* local_channel_column = mActionTable->getColumn(1); +} - for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = contents.rows.begin(); - row_it != contents.rows.end(); - ++row_it) +void LLPanelPreferenceGameControl::populateMappingTableCells(LLScrollListCtrl* target, + const std::vector<U8>& mappings, const LLComboBox* source) +{ + std::vector<LLScrollListItem*> rows = target->getAllData(); + std::vector<LLScrollListItem*> items = source->getAllData(); + llassert(rows.size() == mappings.size()); + + for (size_t i = 0; i < rows.size(); ++i) { - std::string name = row_it->value.getValue().asString(); - if (!name.empty() && name != "menu_separator") - { - LLScrollListItem::Params item_params(*row_it); - item_params.enabled.setValue(true); - size_t num_columns = item_params.columns.size(); - // item_params should already have one column that was defined - // in XUI config file, and now we want to add two more - if (num_columns > 0) - { - LLGameControl::InputChannel channel = LLGameControl::getChannelByActionName(name); + U8 mapping = mappings[i]; + llassert(mapping < items.size()); + // Default values should look as empty cells + rows[i]->getColumn(1)->setValue(mapping == i ? LLSD() : + items[mapping]->getColumn(0)->getValue()); + } +} - cell_params.column = local_channel_column->mName; - cell_params.value = channel.getLocalName(); - item_params.columns.add(cell_params); +LLGameControl::Options& LLPanelPreferenceGameControl::getSelectedDeviceOptions() +{ + auto options_it = mDeviceOptions.find(mSelectedDeviceGUID); + llassert_always(options_it != mDeviceOptions.end()); + return options_it->second.options; +} - // TODO?: add a column with more human readable name - //cell_params.column = remote_channel_column->mName; - //cell_params.value = channel.getRemoteName(); - //item_params.columns.add(cell_params); +// static +std::string LLPanelPreferenceGameControl::getChannelLabel(const std::string& channel_name, + const std::vector<LLScrollListItem*>& items) +{ + if (!channel_name.empty() && channel_name != "NONE") + { + for (LLScrollListItem* item : items) + { + if (item->getValue().asString() == channel_name) + { + if (item->getNumColumns()) + { + return item->getColumn(0)->getValue().asString(); + } + break; } - mActionTable->addRow(item_params, EAddPosition::ADD_BOTTOM); } - else + } + return LLStringUtil::null; +} + +// static +void LLPanelPreferenceGameControl::setNumericLabel(LLScrollListCell* cell, S32 value) +{ + // Default values should look as empty cells + cell->setValue(value ? llformat("%d ", value) : LLStringUtil::null); +} + +void LLPanelPreferenceGameControl::fitInRect(LLUICtrl* ctrl, LLScrollListCtrl* grid, S32 row_index, S32 col_index) +{ + LLRect rect(grid->getCellRect(row_index, col_index)); + LLView* parent = grid->getParent(); + while (parent && parent != ctrl->getParent()) + { + rect.translate(parent->getRect().mLeft, parent->getRect().mBottom); + parent = parent->getParent(); + } + + ctrl->setRect(rect); + rect.translate(-rect.mLeft, -rect.mBottom); + for (LLView* child : *ctrl->getChildList()) + { + LLRect childRect(child->getRect()); + childRect.intersectWith(rect); + if (childRect.mRight < rect.mRight && + childRect.mRight > (rect.mLeft + rect.mRight) / 2) { - // Separator example: - // <rows - // enabled = "false"> - // <columns - // type = "icon" - // color = "0 0 0 0.7" - // halign = "center" - // value = "menu_separator" - // column = "action" / > - //</rows> - mActionTable->addRow(*row_it, EAddPosition::ADD_BOTTOM); + childRect.mRight = rect.mRight; } + child->setRect(childRect); } } void LLPanelPreferenceGameControl::clearSelectionState() { - if (gSelectedCell) - { - mChannelSelector->setVisible(FALSE); - gSelectedCell = nullptr; - } + gSelectedGrid = nullptr; gSelectedItem = nullptr; + gSelectedCell = nullptr; + mNumericValueEditor->setVisible(FALSE); + mAnalogChannelSelector->setVisible(FALSE); + mBinaryChannelSelector->setVisible(FALSE); + mAxisSelector->setVisible(FALSE); } -void LLPanelPreferenceGameControl::addTableSeparator() +void LLPanelPreferenceGameControl::addActionTableSeparator() { LLScrollListItem::Params separator_params; separator_params.enabled(false); @@ -3489,11 +3875,116 @@ void LLPanelPreferenceGameControl::addTableSeparator() mActionTable->addRow(separator_params, EAddPosition::ADD_BOTTOM); } -void LLPanelPreferenceGameControl::updateTable() +void LLPanelPreferenceGameControl::updateActionTableState() { + // Enable the table if at least one of the GameControl<-->Agent options is enabled + bool enable_table = mCheckGameControlToAgent->get() || mCheckAgentToGameControl->get(); + mActionTable->deselectAllItems(); + mActionTable->setEnabled(enable_table); + mAnalogChannelSelector->setVisible(FALSE); + mBinaryChannelSelector->setVisible(FALSE); } +void LLPanelPreferenceGameControl::onResetToDefaults() +{ + clearSelectionState(); + if (mTabChannelMappings->getVisible()) + { + resetChannelMappingsToDefaults(); + } + else if (mTabDeviceSettings->getVisible() && !mSelectedDeviceGUID.empty()) + { + if (mTabAxisOptions->getVisible()) + { + resetAxisOptionsToDefaults(); + } + else if (mTabAxisMappings->getVisible()) + { + resetAxisMappingsToDefaults(); + } + else if (mTabButtonMappings->getVisible()) + { + resetButtonMappingsToDefaults(); + } + } +} + +void LLPanelPreferenceGameControl::resetChannelMappingsToDefaults() +{ + std::vector<std::pair<std::string, LLGameControl::InputChannel>> mappings; + LLGameControl::getDefaultMappings(mappings); + std::vector<LLScrollListItem*> rows = mActionTable->getAllData(); + std::vector<LLScrollListItem*> axes = mAnalogChannelSelector->getAllData(); + std::vector<LLScrollListItem*> btns = mBinaryChannelSelector->getAllData(); + for (LLScrollListItem* row : rows) + { + if (row->getNumColumns() >= 2) // Skip separators + { + std::string action_name = row->getValue().asString(); + if (!action_name.empty() && action_name != "menu_separator") + { + std::string channel_label; + for (const auto& mapping : mappings) + { + if (mapping.first == action_name) + { + std::string channel_name = mapping.second.getLocalName(); + channel_label = + mapping.second.isAxis() ? getChannelLabel(channel_name, axes) : + mapping.second.isButton() ? getChannelLabel(channel_name, btns) : + LLStringUtil::null; + break; + } + } + row->getColumn(1)->setValue(channel_label); + } + } + } +} + +void LLPanelPreferenceGameControl::resetAxisOptionsToDefaults() +{ + std::vector<LLScrollListItem*> rows = mAxisOptions->getAllData(); + llassert(rows.size() == LLGameControl::NUM_AXES); + LLGameControl::Options& options = getSelectedDeviceOptions(); + llassert(options.getAxisOptions().size() == LLGameControl::NUM_AXES); + for (U8 i = 0; i < LLGameControl::NUM_AXES; ++i) + { + rows[i]->getColumn(1)->setValue(false); + rows[i]->getColumn(2)->setValue(LLStringUtil::null); + rows[i]->getColumn(3)->setValue(LLStringUtil::null); + options.getAxisOptions()[i].resetToDefaults(); + } +} + +void LLPanelPreferenceGameControl::resetAxisMappingsToDefaults() +{ + std::vector<LLScrollListItem*> rows = mAxisMappings->getAllData(); + llassert(rows.size() == LLGameControl::NUM_AXES); + LLGameControl::Options& options = getSelectedDeviceOptions(); + llassert(options.getAxisMap().size() == LLGameControl::NUM_AXES); + for (U8 i = 0; i < LLGameControl::NUM_AXES; ++i) + { + rows[i]->getColumn(1)->setValue(LLStringUtil::null); + options.getAxisMap()[i] = i; + } +} + +void LLPanelPreferenceGameControl::resetButtonMappingsToDefaults() +{ + std::vector<LLScrollListItem*> rows = mButtonMappings->getAllData(); + llassert(rows.size() == LLGameControl::NUM_BUTTONS); + LLGameControl::Options& options = getSelectedDeviceOptions(); + llassert(options.getButtonMap().size() == LLGameControl::NUM_BUTTONS); + for (U8 i = 0; i < LLGameControl::NUM_BUTTONS; ++i) + { + rows[i]->getColumn(1)->setValue(LLStringUtil::null); + options.getButtonMap()[i] = i; + } +} + +//------------------------LLFloaterPreferenceProxy-------------------------------- LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key) : LLFloater(key), @@ -3538,7 +4029,7 @@ void LLFloaterPreferenceProxy::onOpen(const LLSD& key) void LLFloaterPreferenceProxy::onClose(bool app_quitting) { - if(app_quitting) + if (app_quitting) { cancel(); } @@ -3562,7 +4053,7 @@ void LLFloaterPreferenceProxy::saveSettings() mSavedValues.clear(); std::list<LLView*> view_stack; view_stack.push_back(this); - while(!view_stack.empty()) + while (!view_stack.empty()) { // Process view on top of the stack LLView* curview = view_stack.front(); @@ -3649,13 +4140,9 @@ void LLFloaterPreferenceProxy::onClickCloseBtn(bool app_quitting) void LLFloaterPreferenceProxy::cancel() { - - for (control_values_map_t::iterator iter = mSavedValues.begin(); - iter != mSavedValues.end(); ++iter) + for (const auto& iter : mSavedValues) { - LLControlVariable* control = iter->first; - LLSD ctrl_value = iter->second; - control->set(ctrl_value); + iter.first->set(iter.second); } mSocksSettingsDirty = false; closeFloater(); @@ -3680,21 +4167,20 @@ void LLFloaterPreferenceProxy::onChangeSocksSettings() // Check for invalid states for the other HTTP proxy radio LLRadioGroup* otherHttpProxy = getChild<LLRadioGroup>("other_http_proxy_type"); if ((otherHttpProxy->getSelectedValue().asString() == "Socks" && - !getChild<LLCheckBoxCtrl>("socks_proxy_enabled")->get())||( + getChild<LLCheckBoxCtrl>("socks_proxy_enabled")->get() == false )||( otherHttpProxy->getSelectedValue().asString() == "Web" && - !getChild<LLCheckBoxCtrl>("web_proxy_enabled")->get())) + getChild<LLCheckBoxCtrl>("web_proxy_enabled")->get() == false ) ) { otherHttpProxy->selectFirstItem(); } - } void LLFloaterPreference::onUpdateFilterTerm(bool force) { - LLWString seachValue = utf8str_to_wstring(mFilterEdit->getValue()); - LLWStringUtil::toLower(seachValue); + LLWString seachValue = utf8str_to_wstring( mFilterEdit->getValue() ); + LLWStringUtil::toLower( seachValue ); - if (!mSearchData || (mSearchData->mLastFilter == seachValue && !force)) + if( !mSearchData || (mSearchData->mLastFilter == seachValue && !force)) return; if (mSearchDataDirty) @@ -3705,7 +4191,7 @@ void LLFloaterPreference::onUpdateFilterTerm(bool force) mSearchData->mLastFilter = seachValue; - if (!mSearchData->mRootTab) + if( !mSearchData->mRootTab ) return; mSearchData->mRootTab->hightlightAndHide( seachValue ); @@ -3728,10 +4214,10 @@ void LLFloaterPreference::filterIgnorableNotifications() void collectChildren( LLView const *aView, ll::prefs::PanelDataPtr aParentPanel, ll::prefs::TabContainerDataPtr aParentTabContainer ) { - if (!aView) + if( !aView ) return; - llassert_always(aParentPanel || aParentTabContainer); + llassert_always( aParentPanel || aParentTabContainer ); for (LLView* pView : *aView->getChildList()) { @@ -3741,56 +4227,56 @@ void collectChildren( LLView const *aView, ll::prefs::PanelDataPtr aParentPanel, ll::prefs::PanelDataPtr pCurPanelData = aParentPanel; ll::prefs::TabContainerDataPtr pCurTabContainer = aParentTabContainer; - LLPanel const *pPanel = dynamic_cast<LLPanel const*>(pView); - LLTabContainer const *pTabContainer = dynamic_cast<LLTabContainer const*>(pView); - ll::ui::SearchableControl const *pSCtrl = dynamic_cast<ll::ui::SearchableControl const*>( pView ); + LLPanel const *pPanel = dynamic_cast< LLPanel const *>( pView ); + LLTabContainer const *pTabContainer = dynamic_cast< LLTabContainer const *>( pView ); + ll::ui::SearchableControl const *pSCtrl = dynamic_cast< ll::ui::SearchableControl const *>( pView ); - if (pTabContainer) + if( pTabContainer ) { pCurPanelData.reset(); - pCurTabContainer = ll::prefs::TabContainerDataPtr(new ll::prefs::TabContainerData); - pCurTabContainer->mTabContainer = const_cast< LLTabContainer *>(pTabContainer); + pCurTabContainer = ll::prefs::TabContainerDataPtr( new ll::prefs::TabContainerData ); + pCurTabContainer->mTabContainer = const_cast< LLTabContainer *>( pTabContainer ); pCurTabContainer->mLabel = pTabContainer->getLabel(); pCurTabContainer->mPanel = 0; - if (aParentPanel) - aParentPanel->mChildPanel.push_back(pCurTabContainer); - if (aParentTabContainer) - aParentTabContainer->mChildPanel.push_back(pCurTabContainer); + if( aParentPanel ) + aParentPanel->mChildPanel.push_back( pCurTabContainer ); + if( aParentTabContainer ) + aParentTabContainer->mChildPanel.push_back( pCurTabContainer ); } - else if (pPanel) + else if( pPanel ) { pCurTabContainer.reset(); - pCurPanelData = ll::prefs::PanelDataPtr(new ll::prefs::PanelData); + pCurPanelData = ll::prefs::PanelDataPtr( new ll::prefs::PanelData ); pCurPanelData->mPanel = pPanel; pCurPanelData->mLabel = pPanel->getLabel(); llassert_always( aParentPanel || aParentTabContainer ); - if (aParentTabContainer) - aParentTabContainer->mChildPanel.push_back(pCurPanelData); - else if (aParentPanel) - aParentPanel->mChildPanel.push_back(pCurPanelData); + if( aParentTabContainer ) + aParentTabContainer->mChildPanel.push_back( pCurPanelData ); + else if( aParentPanel ) + aParentPanel->mChildPanel.push_back( pCurPanelData ); } - else if (pSCtrl && pSCtrl->getSearchText().size()) + else if( pSCtrl && pSCtrl->getSearchText().size() ) { - ll::prefs::SearchableItemPtr item = ll::prefs::SearchableItemPtr(new ll::prefs::SearchableItem()); + ll::prefs::SearchableItemPtr item = ll::prefs::SearchableItemPtr( new ll::prefs::SearchableItem() ); item->mView = pView; item->mCtrl = pSCtrl; - item->mLabel = utf8str_to_wstring(pSCtrl->getSearchText()); - LLWStringUtil::toLower(item->mLabel); + item->mLabel = utf8str_to_wstring( pSCtrl->getSearchText() ); + LLWStringUtil::toLower( item->mLabel ); - llassert_always(aParentPanel || aParentTabContainer); + llassert_always( aParentPanel || aParentTabContainer ); - if (aParentPanel) - aParentPanel->mChildren.push_back(item); - if (aParentTabContainer) - aParentTabContainer->mChildren.push_back(item); + if( aParentPanel ) + aParentPanel->mChildren.push_back( item ); + if( aParentTabContainer ) + aParentTabContainer->mChildren.push_back( item ); } - collectChildren(pView, pCurPanelData, pCurTabContainer); + collectChildren( pView, pCurPanelData, pCurTabContainer ); } } diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index 22421f296c..810bb7d6ac 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -36,10 +36,13 @@ #include "llfloater.h" #include "llavatarpropertiesprocessor.h" #include "llconversationlog.h" -#include "llgamecontroltranslator.h" +#include "llgamecontrol.h" +#include "llkeyconflict.h" +#include "llscrolllistcell.h" +#include "llscrolllistctrl.h" #include "llsearcheditor.h" #include "llsetkeybinddialog.h" -#include "llkeyconflict.h" +#include "llspinctrl.h" class LLConversationLogObserver; class LLPanelPreference; @@ -102,6 +105,7 @@ public: static void updateShowFavoritesCheckbox(bool val); void processProperties( void* pData, EAvatarProcessorType type ) override; + void saveAvatarProperties( void ); static void saveAvatarPropertiesCoro(const std::string url, bool allow_publish); void selectPrivacyPanel(); void selectChatPanel(); @@ -306,8 +310,6 @@ public: void setHardwareDefaults() override; void setPresetText(); - static const std::string getPresetsPath(); - protected: bool hasDirtyChilds(); @@ -372,6 +374,7 @@ private: class LLPanelPreferenceGameControl : public LLPanelPreference { + LOG_CLASS(LLPanelPreferenceGameControl); public: enum InputType @@ -384,41 +387,93 @@ public: LLPanelPreferenceGameControl(); ~LLPanelPreferenceGameControl(); - void apply() override; - void loadDefaults(); - void loadSettings(); + void onOpen(const LLSD& key) override; void saveSettings() override; - void updateEnabledState(); - void onClickGameControlToServer(LLUICtrl* ctrl); - // "Agent" in this context means either Avatar or Flycam - void onClickGameControlToAgent(LLUICtrl* ctrl); - void onClickAgentToGameControl(LLUICtrl* ctrl); - void onActionSelect(); - void onCommitInputChannel(); + void onGridSelect(LLUICtrl* ctrl); + void onCommitInputChannel(LLUICtrl* ctrl); + + void onAxisOptionsSelect(); + void onCommitNumericValue(); static bool isWaitingForInputChannel(); - static void applyGameControlInput(const LLGameControl::InputChannel& channel); + static void applyGameControlInput(); + protected: bool postBuild() override; - void populateActionTable(); - void populateColumns(); - void populateRows(const std::string& filename); + void populateActionTableRows(const std::string& filename); + void populateActionTableCells(); + static bool parseXmlFile(LLScrollListCtrl::Contents& contents, + const std::string& filename, const std::string& what); + + void populateDeviceTitle(); + void populateDeviceSettings(const std::string& guid); + void populateOptionsTableRows(); + void populateOptionsTableCells(); + void populateMappingTableRows(LLScrollListCtrl* target, + const LLComboBox* source, size_t row_count); + void populateMappingTableCells(LLScrollListCtrl* target, + const std::vector<U8>& mappings, const LLComboBox* source); + LLGameControl::Options& getSelectedDeviceOptions(); + + static std::string getChannelLabel(const std::string& channelName, + const std::vector<LLScrollListItem*>& items); + static void setNumericLabel(LLScrollListCell* cell, S32 value); + static void fitInRect(LLUICtrl* ctrl, LLScrollListCtrl* grid, S32 row_index, S32 col_index); private: + bool initCombobox(LLScrollListItem* item, LLScrollListCtrl* grid); void clearSelectionState(); - void addTableSeparator(); - void updateTable(); - LOG_CLASS(LLPanelPreferenceGameControl); - + void addActionTableSeparator(); + void updateActionTableState(); + void onResetToDefaults(); + void resetChannelMappingsToDefaults(); + void resetAxisOptionsToDefaults(); + void resetAxisMappingsToDefaults(); + void resetButtonMappingsToDefaults(); + + // Above the tab container LLCheckBoxCtrl *mCheckGameControlToServer; // send game_control data to server LLCheckBoxCtrl *mCheckGameControlToAgent; // use game_control data to move avatar LLCheckBoxCtrl *mCheckAgentToGameControl; // translate external avatar actions to game_control data + // 1st tab "Channel mappings" + LLPanel* mTabChannelMappings; LLScrollListCtrl* mActionTable; - LLComboBox* mChannelSelector; - LLGameControlTranslator mActionTranslator; + + // 2nd tab "Device settings" + LLPanel* mTabDeviceSettings; + LLTextBox* mNoDeviceMessage; + LLTextBox* mDevicePrompt; + LLTextBox* mSingleDevice; + LLComboBox* mDeviceList; + LLCheckBoxCtrl* mCheckShowAllDevices; + LLPanel* mPanelDeviceSettings; + LLPanel* mTabAxisOptions; + LLScrollListCtrl* mAxisOptions; + LLPanel* mTabAxisMappings; + LLScrollListCtrl* mAxisMappings; + LLPanel* mTabButtonMappings; + LLScrollListCtrl* mButtonMappings; + + LLButton* mResetToDefaults; + + // Numeric value editor + LLSpinCtrl* mNumericValueEditor; + + // Channel selectors + LLComboBox* mAnalogChannelSelector; + LLComboBox* mBinaryChannelSelector; + LLComboBox* mAxisSelector; + + struct DeviceOptions + { + std::string name, settings; + LLGameControl::Options options; + }; + std::map<std::string, DeviceOptions> mDeviceOptions; + std::string mSelectedDeviceGUID; }; class LLAvatarComplexityControls diff --git a/indra/newview/skins/default/xui/en/game_control_table_columns.xml b/indra/newview/skins/default/xui/en/game_control_table_columns.xml deleted file mode 100644 index f88fc8305c..0000000000 --- a/indra/newview/skins/default/xui/en/game_control_table_columns.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes" ?> -<contents> - <columns - relative_width="0.25" - label="Action" - name="action" /> - <columns - relative_width="0.25" - label="GameControl" - name="index" /> - <columns - relative_width="0.50" - label=" " - name="foo" /> -</contents> diff --git a/indra/newview/skins/default/xui/en/game_control_table_rows.xml b/indra/newview/skins/default/xui/en/game_control_table_rows.xml index b09346e83e..181bc8592c 100644 --- a/indra/newview/skins/default/xui/en/game_control_table_rows.xml +++ b/indra/newview/skins/default/xui/en/game_control_table_rows.xml @@ -63,7 +63,6 @@ name="action" value="Look Down" /> </rows> - <rows name="stop" value="stop"> diff --git a/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml b/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml index 074e27215d..1815d98ae0 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml @@ -10,186 +10,384 @@ top="1" width="517"> <check_box + name="game_control_to_server" control_name="GameControlToServer" - follows="top|left" - height="15" label="Send GameControl Data to server" layout="topleft" + height="15" left="10" - name="game_control_to_server" - top="6" - width="232"/> + top="10"/> <check_box + name="game_control_to_agent" control_name="GameControlToAgent" - follows="top|left" - height="15" label="GameControl moves avatar and flycam" layout="topleft" + height="15" left="10" - name="game_control_to_agent" - top="27" - width="232"/> + top="30"/> <check_box + name="agent_to_game_control" control_name="AgentToGameControl" - follows="top|left" - height="15" label="Avatar actions interpreted as GameControl" layout="topleft" + height="15" left="10" - name="agent_to_game_control" - top="48" - width="232"/> - <scroll_list - draw_heading="true" + top="50"/> + <tab_container + name="game_control_tabs" + layout="topleft" follows="all" + top="70" + left="2" + right="-2" + bottom="-32"> + <panel + name="tab_channel_mappings" + label="Channel mappings"> + <scroll_list + name="action_table" + layout="topleft" + follows="all" + top="1" + left="6" + right="-5" + bottom="-4" + can_sort="false" + column_padding="0" + multi_select="false" + selection_type="header" + fg_disable_color="ScrollUnselectedColor"> + <scroll_list.columns + name="action" + label="Action" + relative_width="0.25" /> + <scroll_list.columns + label="Channel" + relative_width="0.75" /> + </scroll_list> + </panel> + <panel + name="tab_device_settings" + label="Device settings"> + <text + type="string" + name="nodevice_message" + layout="topleft" + follows="all" + halign="center" + valign="center" + top="1" + left="1" + right="-1" + bottom="-1">No device</text> + <panel + layout="topleft" + follows="top|left|right" + top="1" + left="1" + right="-1" + height="60"> + <text + type="string" + name="device_prompt" + layout="topleft" + follows="top|left" + valign="center" + top="10" + left="2" + height="21" + width="200">Device to customize:</text> + <text + type="string" + name="single_device" + layout="topleft" + follows="top|left" + valign="center" + top="30" + left="2" + height="21" + width="500"/> + <combo_box + name="device_list" + layout="topleft" + follows="top|left" + top="30" + left="2" + width="500"/> + </panel> + <!-- This checkbox should be placed inside the above panel --> + <check_box + name="show_all_known_devices" + label="Show all known devices" + layout="topleft" + follows="top|left" + top_delta="10" + left="310"/> + <panel + name="device_settings" + layout="topleft" + follows="all" + top="61" + left="1" + right="-1" + bottom="-1"> + <tab_container + name="device_settings_tabs" + layout="topleft" + follows="all" + top="1" + left="1" + right="-1" + bottom="-1"> + <panel + name="tab_axis_options" + label="Axis Options"> + <scroll_list + name="axis_options" + follows="all" + top="41" + left="1" + right="-1" + bottom="-1" + can_sort="false" + column_padding="0" + draw_heading="true" + multi_select="false" + selection_type="header" + fg_disable_color="ScrollUnselectedColor"> + <scroll_list.columns + label="Axis" + relative_width="0.5"/> + <scroll_list.columns + label="Invert" + relative_width="0.1"/> + <scroll_list.columns + label="Dead Zone" + relative_width="0.2"/> + <scroll_list.columns + label="Offset" + relative_width="0.2"/> + </scroll_list> + </panel> + <panel + name="tab_axis_mappings" + label="Axis Mappings"> + <scroll_list + name="axis_mappings" + follows="all" + top="1" + left="1" + right="-1" + bottom="-1" + can_sort="false" + column_padding="0" + multi_select="false" + selection_type="header" + fg_disable_color="ScrollUnselectedColor"> + <scroll_list.columns relative_width="0.5"/> + <scroll_list.columns relative_width="0.5"/> + </scroll_list> + </panel> + <panel + name="tab_button_mappings" + label="Button Mappings"> + <scroll_list + name="button_mappings" + follows="all" + top="1" + left="1" + right="-1" + bottom="-1" + can_sort="false" + column_padding="0" + multi_select="false" + selection_type="header" + fg_disable_color="ScrollUnselectedColor"> + <scroll_list.columns relative_width="0.5"/> + <scroll_list.columns relative_width="0.5"/> + </scroll_list> + </panel> + </tab_container> + </panel> + </panel> + </tab_container> + <panel layout="topleft" - column_padding="0" - selection_type="header" - top="66" - left="3" - bottom="-3" - right="-3" - can_sort="false" - multi_select="false" - name="action_table" - fg_disable_color="ScrollUnselectedColor"/> + follows="top|left" + top="405" + height="41" + left="1" + right="-1"> + <button + layout="topleft" + follows="top|left" + name="reset_to_defaults" + label="Reset to Defaults" + top="1" + left="8" + width="200"/> + </panel> + <spinner + name="numeric_value_editor" + decimal_digits="0" + increment="1" + width="100" + height="18"/> <combo_box - height="23" - layout="topleft" - left="10" - name="input_channel_combo" - top_pad="5" - width="90"> + name="axis_selector" + width="250" + height="18"> + <combo_box.item label="AXIS_0 (Left stick: tilt left/right)"/> + <combo_box.item label="AXIS_1 (Left stick: tilt forward/back)"/> + <combo_box.item label="AXIS_2 (Right stick: tilt left/right)"/> + <combo_box.item label="AXIS_3 (Right stick: tilt forward/back)"/> + <combo_box.item label="AXIS_4 (Left trigger: push/release)"/> + <combo_box.item label="AXIS_5 (Right trigger: push/release)"/> + <combo_box.item label="NONE (No mapping)"/> + </combo_box> + <combo_box + name="analog_channel_selector" + width="380" + height="18"> <combo_box.item - label="AXIS_0-" + label="AXIS_0- (Left stick: tilt right)" name="AXIS_0-" value="AXIS_0-"/> <combo_box.item - label="AXIS_0+" + label="AXIS_0+ (Left stick: tilt left)" name="AXIS_0+" value="AXIS_0+"/> <combo_box.item - label="AXIS_1-" + label="AXIS_1- (Left stick: tilt back)" name="AXIS_1-" value="AXIS_1-"/> <combo_box.item - label="AXIS_1+" + label="AXIS_1+ (Left stick: tilt forward)" name="AXIS_1+" value="AXIS_1+"/> <combo_box.item - label="AXIS_2-" + label="AXIS_2- (Right stick: tilt right)" name="AXIS_2-" value="AXIS_2-"/> <combo_box.item - label="AXIS_2+" + label="AXIS_2+ (Right stick: tilt left)" name="AXIS_2+" value="AXIS_2+"/> <combo_box.item - label="AXIS_3-" + label="AXIS_3- (Right stick: tilt back)" name="AXIS_3-" value="AXIS_3-"/> <combo_box.item - label="AXIS_3+" + label="AXIS_3+ (Right stick: tilt forward)" name="AXIS_3+" value="AXIS_3+"/> <combo_box.item - label="AXIS_4-" + label="AXIS_4- (Left trigger: release)" name="AXIS_4-" value="AXIS_4-"/> <combo_box.item - label="AXIS_4+" + label="AXIS_4+ (Left trigger: push)" name="AXIS_4+" value="AXIS_4+"/> <combo_box.item - label="AXIS_5-" + label="AXIS_5- (Right trigger: release)" name="AXIS_5-" value="AXIS_5-"/> <combo_box.item - label="AXIS_5+" + label="AXIS_5+ (Right trigger: push)" name="AXIS_5+" value="AXIS_5+"/> <combo_box.item - label="BUTTON_0" + label="NONE (No mapping)" + name="NONE" + value="NONE"/> + </combo_box> + <combo_box + name="binary_channel_selector" + width="380" + height="18"> + <combo_box.item + label="BUTTON_0 ('A' button)" name="BUTTON_0" value="BUTTON_0"/> <combo_box.item - label="BUTTON_1" + label="BUTTON_1 ('B' button)" name="BUTTON_1" value="BUTTON_1"/> <combo_box.item - label="BUTTON_2" + label="BUTTON_2 ('X' button)" name="BUTTON_2" value="BUTTON_2"/> <combo_box.item - label="BUTTON_3" + label="BUTTON_3 ('Y' button)" name="BUTTON_3" value="BUTTON_3"/> <combo_box.item - label="BUTTON_4" + label="BUTTON_4 (Back button)" name="BUTTON_4" value="BUTTON_4"/> <combo_box.item - label="BUTTON_5" + label="BUTTON_5 (Guide button)" name="BUTTON_5" value="BUTTON_5"/> <combo_box.item - label="BUTTON_6" + label="BUTTON_6 (Home button)" name="BUTTON_6" value="BUTTON_6"/> <combo_box.item - label="BUTTON_7" + label="BUTTON_7 (Left stick)" name="BUTTON_7" value="BUTTON_7"/> <combo_box.item - label="BUTTON_8" + label="BUTTON_8 (Right stick)" name="BUTTON_8" value="BUTTON_8"/> <combo_box.item - label="BUTTON_9" + label="BUTTON_9 (Left shoulder)" name="BUTTON_9" value="BUTTON_9"/> <combo_box.item - label="BUTTON_10" + label="BUTTON_10 (Right shoulder)" name="BUTTON_10" value="BUTTON_10"/> <combo_box.item - label="BUTTON_11" + label="BUTTON_11 (DPad up)" name="BUTTON_11" value="BUTTON_11"/> <combo_box.item - label="BUTTON_12" + label="BUTTON_12 (DPad down)" name="BUTTON_12" value="BUTTON_12"/> <combo_box.item - label="BUTTON_13" + label="BUTTON_13 (DPad left)" name="BUTTON_13" value="BUTTON_13"/> <combo_box.item - label="BUTTON_14" + label="BUTTON_14 (DPad right)" name="BUTTON_14" value="BUTTON_14"/> <combo_box.item - label="BUTTON_15" + label="BUTTON_15 (Miscellaneous 1)" name="BUTTON_15" value="BUTTON_15"/> <combo_box.item - label="BUTTON_16" + label="BUTTON_16 (Paddle 1)" name="BUTTON_16" value="BUTTON_16"/> <combo_box.item - label="BUTTON_17" + label="BUTTON_17 (Paddle 2)" name="BUTTON_17" value="BUTTON_17"/> <combo_box.item - label="BUTTON_18" + label="BUTTON_18 (Paddle 3)" name="BUTTON_18" value="BUTTON_18"/> <combo_box.item - label="BUTTON_19" + label="BUTTON_19 (Paddle 4)" name="BUTTON_19" value="BUTTON_19"/> <combo_box.item - label="BUTTON_20" + label="BUTTON_20 (Touchpad)" name="BUTTON_20" value="BUTTON_20"/> <combo_box.item @@ -237,7 +435,7 @@ name="BUTTON_31" value="BUTTON_31"/> <combo_box.item - label="NONE" + label="NONE (No mapping)" name="NONE" value="NONE"/> </combo_box> diff --git a/scripts/messages/message_template.msg b/scripts/messages/message_template.msg index 4bbcbd369d..e1f22343be 100755 --- a/scripts/messages/message_template.msg +++ b/scripts/messages/message_template.msg @@ -6,9 +6,9 @@ version 2.0 // numbers. Each message must be numbered relative to the // other messages of that type. The current highest number // for each type is listed below: -// Low: 431 +// Low: 430 // Medium: 18 -// High: 30 +// High: 32 // PLEASE UPDATE THIS WHEN YOU ADD A NEW MESSAGE! @@ -9149,7 +9149,7 @@ version 2.0 // message will be resent a few times in the hopes the server finally receives it. // { - GameControlInput Medium 431 NotTrusted Zerocoded + GameControlInput High 32 NotTrusted Zerocoded { AgentData Single { AgentID LLUUID } |