summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorAlexander Gavriliuk <alexandrgproductengine@lindenlab.com>2024-05-08 23:32:58 +0200
committerAndrew Meadows <andrew.l.meadows@gmail.com>2024-10-03 09:02:09 -0700
commit2daf175650cdda7cc8f820b6cb17b1475496e7ac (patch)
tree0ece9bb592a922fbcb3f4532aee10941e307f44f /indra
parentec39ac89e8529da206dafd519d75ad5944888076 (diff)
Add GameControl UI for per device settings
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/llinitparam.h13
-rw-r--r--indra/llcommon/llsd.cpp25
-rw-r--r--indra/llcommon/llsd.h15
-rw-r--r--indra/llcommon/llstring.cpp23
-rw-r--r--indra/llcommon/llstring.h62
-rw-r--r--indra/llui/llcombobox.cpp52
-rw-r--r--indra/llui/llcombobox.h1
-rw-r--r--indra/llui/llscrolllistctrl.cpp111
-rw-r--r--indra/llui/llscrolllistctrl.h1
-rw-r--r--indra/llwindow/llgamecontrol.cpp1638
-rw-r--r--indra/llwindow/llgamecontrol.h156
-rw-r--r--indra/llwindow/llgamecontroltranslator.cpp231
-rw-r--r--indra/llwindow/llgamecontroltranslator.h9
-rw-r--r--indra/llwindow/llkeyboard.cpp18
-rw-r--r--indra/llwindow/llkeyboard.h6
-rw-r--r--indra/llwindow/llkeyboardmacosx.cpp12
-rw-r--r--indra/llwindow/llkeyboardmacosx.h2
-rw-r--r--indra/llwindow/llkeyboardsdl.cpp8
-rw-r--r--indra/llwindow/llkeyboardsdl.h2
-rw-r--r--indra/llwindow/llkeyboardwin32.cpp9
-rw-r--r--indra/llwindow/llkeyboardwin32.h2
-rw-r--r--indra/llwindow/llwindow.cpp2
-rw-r--r--indra/newview/app_settings/settings.xml54
-rw-r--r--indra/newview/llappviewer.cpp61
-rw-r--r--indra/newview/llfloaterpreference.cpp1178
-rw-r--r--indra/newview/llfloaterpreference.h103
-rw-r--r--indra/newview/skins/default/xui/en/game_control_table_columns.xml15
-rw-r--r--indra/newview/skins/default/xui/en/game_control_table_rows.xml1
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_game_control.xml332
29 files changed, 3025 insertions, 1117 deletions
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>