Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the KBM UI to use a Grid based layout rather than stack panels #2299

39 changes: 25 additions & 14 deletions src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,20 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
Windows::UI::Xaml::Controls::StackPanel xamlContainer;

// Header for the window
Windows::UI::Xaml::Controls::StackPanel header;
header.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
Windows::UI::Xaml::Controls::RelativePanel header;
header.Margin({ 10, 10, 10, 30 });
header.Spacing(10);

// Header text
TextBlock headerText;
headerText.Text(L"Remap Keyboard");
headerText.FontSize(30);
headerText.Margin({ 0, 0, 1000, 0 });
headerText.Margin({ 0, 0, 0, 0 });
header.SetAlignLeftWithPanel(headerText, true);

// Header Cancel button
Button cancelButton;
cancelButton.Content(winrt::box_value(L"Cancel"));
cancelButton.Margin({ 0, 0, 10, 0 });
cancelButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
// Close the window since settings do not need to be saved
PostMessage(_hWndEditKeyboardWindow, WM_CLOSE, 0, 0);
Expand All @@ -102,30 +102,37 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
keyRemapInfoHeader.Margin({ 10, 0, 0, 10 });

// Table to display the key remaps
Windows::UI::Xaml::Controls::StackPanel keyRemapTable;
Grid keyRemapTable;
ColumnDefinition firstColumn;
ColumnDefinition secondColumn;
ColumnDefinition thirdColumn;
keyRemapTable.Margin({ 10, 10, 10, 20 });
keyRemapTable.Spacing(10);

// Header row of the keys remap table
Windows::UI::Xaml::Controls::StackPanel tableHeaderRow;
tableHeaderRow.Spacing(100);
tableHeaderRow.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
keyRemapTable.HorizontalAlignment(HorizontalAlignment::Stretch);
keyRemapTable.ColumnSpacing(10);
keyRemapTable.ColumnDefinitions().Append(firstColumn);
keyRemapTable.ColumnDefinitions().Append(secondColumn);
keyRemapTable.ColumnDefinitions().Append(thirdColumn);
keyRemapTable.RowDefinitions().Append(RowDefinition());

// First header textblock in the header row of the keys remap table
TextBlock originalKeyRemapHeader;
originalKeyRemapHeader.Text(L"Original Key:");
originalKeyRemapHeader.FontWeight(Text::FontWeights::Bold());
originalKeyRemapHeader.Margin({ 0, 0, 0, 10 });
tableHeaderRow.Children().Append(originalKeyRemapHeader);

// Second header textblock in the header row of the keys remap table
TextBlock newKeyRemapHeader;
newKeyRemapHeader.Text(L"New Key:");
newKeyRemapHeader.FontWeight(Text::FontWeights::Bold());
newKeyRemapHeader.Margin({ 0, 0, 0, 10 });
tableHeaderRow.Children().Append(newKeyRemapHeader);

keyRemapTable.Children().Append(tableHeaderRow);
keyRemapTable.SetColumn(originalKeyRemapHeader, 0);
keyRemapTable.SetRow(originalKeyRemapHeader, 0);
keyRemapTable.SetColumn(newKeyRemapHeader, 1);
keyRemapTable.SetRow(newKeyRemapHeader, 0);

keyRemapTable.Children().Append(originalKeyRemapHeader);
keyRemapTable.Children().Append(newKeyRemapHeader);

// Message to display success/failure of saving settings.
Flyout applyFlyout;
Expand Down Expand Up @@ -197,6 +204,8 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
// Main Header Apply button
Button applyButton;
applyButton.Content(winrt::box_value(L"Apply"));
header.SetAlignRightWithPanel(applyButton, true);
header.SetLeftOf(cancelButton, applyButton);
applyButton.Flyout(applyFlyout);
applyButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
bool isSuccess = true;
Expand Down Expand Up @@ -315,7 +324,9 @@ LRESULT CALLBACK EditKeyboardWindowProc(HWND hWnd, UINT messageCode, WPARAM wPar
RECT rcClient;
switch (messageCode)
{
// Resize the XAML window whenever the parent window is painted or resized
case WM_PAINT:
case WM_SIZE:
GetClientRect(hWnd, &rcClient);
SetWindowPos(hWndXamlIslandEditKeyboardWindow, 0, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom, SWP_SHOWWINDOW);
break;
Expand Down
37 changes: 24 additions & 13 deletions src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,50 +78,57 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
Windows::UI::Xaml::Controls::StackPanel xamlContainer;

// Header for the window
Windows::UI::Xaml::Controls::StackPanel header;
header.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
Windows::UI::Xaml::Controls::RelativePanel header;
header.Margin({ 10, 10, 10, 30 });
header.Spacing(10);

// Header text
TextBlock headerText;
headerText.Text(L"Edit Shortcuts");
headerText.FontSize(30);
headerText.Margin({ 0, 0, 100, 0 });
header.SetAlignLeftWithPanel(headerText, true);

// Cancel button
Button cancelButton;
cancelButton.Content(winrt::box_value(L"Cancel"));
cancelButton.Margin({ 0, 0, 10, 0 });
cancelButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
// Close the window since settings do not need to be saved
PostMessage(_hWndEditShortcutsWindow, WM_CLOSE, 0, 0);
});

// Table to display the shortcuts
Windows::UI::Xaml::Controls::StackPanel shortcutTable;
Windows::UI::Xaml::Controls::Grid shortcutTable;
ColumnDefinition firstColumn;
ColumnDefinition secondColumn;
ColumnDefinition thirdColumn;
shortcutTable.Margin({ 10, 10, 10, 20 });
shortcutTable.Spacing(10);

// Header row of the shortcut table
Windows::UI::Xaml::Controls::StackPanel tableHeaderRow;
tableHeaderRow.Spacing(100);
tableHeaderRow.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
shortcutTable.HorizontalAlignment(HorizontalAlignment::Stretch);
shortcutTable.ColumnSpacing(10);
shortcutTable.ColumnDefinitions().Append(firstColumn);
shortcutTable.ColumnDefinitions().Append(secondColumn);
shortcutTable.ColumnDefinitions().Append(thirdColumn);
shortcutTable.RowDefinitions().Append(RowDefinition());

// First header textblock in the header row of the shortcut table
TextBlock originalShortcutHeader;
originalShortcutHeader.Text(L"Original Shortcut:");
originalShortcutHeader.FontWeight(Text::FontWeights::Bold());
originalShortcutHeader.Margin({ 0, 0, 0, 10 });
tableHeaderRow.Children().Append(originalShortcutHeader);

// Second header textblock in the header row of the shortcut table
TextBlock newShortcutHeader;
newShortcutHeader.Text(L"New Shortcut:");
newShortcutHeader.FontWeight(Text::FontWeights::Bold());
newShortcutHeader.Margin({ 0, 0, 0, 10 });
tableHeaderRow.Children().Append(newShortcutHeader);

shortcutTable.Children().Append(tableHeaderRow);
shortcutTable.SetColumn(originalShortcutHeader, 0);
shortcutTable.SetRow(newShortcutHeader, 0);
shortcutTable.SetColumn(originalShortcutHeader, 1);
shortcutTable.SetRow(newShortcutHeader, 0);

shortcutTable.Children().Append(originalShortcutHeader);
shortcutTable.Children().Append(newShortcutHeader);

// Message to display success/failure of saving settings.
Flyout applyFlyout;
Expand Down Expand Up @@ -149,6 +156,8 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
// Apply button
Button applyButton;
applyButton.Content(winrt::box_value(L"Apply"));
header.SetAlignRightWithPanel(applyButton, true);
header.SetLeftOf(cancelButton, applyButton);
applyButton.Flyout(applyFlyout);
applyButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
bool isSuccess = true;
Expand Down Expand Up @@ -239,7 +248,9 @@ LRESULT CALLBACK EditShortcutsWindowProc(HWND hWnd, UINT messageCode, WPARAM wPa
RECT rcClient;
switch (messageCode)
{
// Resize the XAML window whenever the parent window is painted or resized
case WM_PAINT:
case WM_SIZE:
GetClientRect(hWnd, &rcClient);
SetWindowPos(hWndXamlIslandEditShortcutsWindow, 0, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom, SWP_SHOWWINDOW);
break;
Expand Down
177 changes: 175 additions & 2 deletions src/modules/keyboardmanager/ui/KeyDropDownControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,178 @@ void KeyDropDownControl::CheckAndUpdateKeyboardLayout(ComboBox currentDropDown,
}
}

// Function to set selection handler for single key remap drop down. Needs to be called after the constructor since the singleKeyControl StackPanel is null if called in the constructor
void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& singleKeyControl, size_t colIndex, std::vector<std::vector<DWORD>>& singleKeyRemapBuffer)
{
dropDown.SelectionChanged([&, table, singleKeyControl, colIndex](winrt::Windows::Foundation::IInspectable const& sender, SelectionChangedEventArgs const& args) {
ComboBox currentDropDown = sender.as<ComboBox>();
int selectedKeyIndex = currentDropDown.SelectedIndex();
// Get row index of the single key control
uint32_t controlIndex;
bool indexFound = table.Children().IndexOf(singleKeyControl, controlIndex);
if (indexFound)
{
int rowIndex = (controlIndex - 2) / 3;
// Check if the element was not found or the index exceeds the known keys
if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex)
{
singleKeyRemapBuffer[rowIndex][colIndex] = keyCodeList[selectedKeyIndex];
}
else
{
// Reset to null if the key is not found
singleKeyRemapBuffer[rowIndex][colIndex] = NULL;
}
}
});
}
Comment on lines +40 to +63
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't thing it is a good idea to put the SelectionHandler code for each Dialog here. The SetSelectionHandler method should just receive a callback function and set it as the handler for the SelectionChanged event.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main problem in moving it, is the keyCodeList vector is a member of the KeyDropDownControl class. So I can't define the lambda outside the class and pass the keyCodeList as a callback since that variable does not exist in the parent class.


// Function to set selection handler for shortcut drop down. Needs to be called after the constructor since the shortcutControl StackPanel is null if called in the constructor
void KeyDropDownControl::SetSelectionHandler(Grid& table, StackPanel& shortcutControl, StackPanel parent, size_t colIndex, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects)
{
Flyout warningFlyout;
TextBlock warningMessage;
warningFlyout.Content(warningMessage);
dropDown.ContextFlyout().SetAttachedFlyout((FrameworkElement)dropDown, warningFlyout);

// drop down selection handler
dropDown.SelectionChanged([&, table, shortcutControl, colIndex, parent, warningMessage](winrt::Windows::Foundation::IInspectable const& sender, SelectionChangedEventArgs const&) {
ComboBox currentDropDown = sender.as<ComboBox>();
int selectedKeyIndex = currentDropDown.SelectedIndex();
uint32_t dropDownIndex = -1;
bool dropDownFound = parent.Children().IndexOf(currentDropDown, dropDownIndex);
// Get row index of the single key control
uint32_t controlIndex;
bool controlIindexFound = table.Children().IndexOf(shortcutControl, controlIndex);

if (controlIindexFound)
{
int rowIndex = (controlIndex - 2) / 3;
if (selectedKeyIndex != -1 && keyCodeList.size() > selectedKeyIndex && dropDownFound)
{
// If only 1 drop down and action key is chosen: Warn that a modifier must be chosen
if (parent.Children().Size() == 1 && !KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]))
{
// warn and reset the drop down
SetDropDownError(currentDropDown, warningMessage, L"Shortcut must start with a modifier key");
}
// If it is the last drop down
else if (dropDownIndex == parent.Children().Size() - 1)
{
// If last drop down and a modifier is selected: add a new drop down (max of 5 drop downs should be enforced)
if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && parent.Children().Size() < 5)
{
// If it matched any of the previous modifiers then reset that drop down
if (CheckRepeatedModifier(parent, dropDownIndex, selectedKeyIndex, keyCodeList))
{
// warn and reset the drop down
SetDropDownError(currentDropDown, warningMessage, L"Shortcut cannot contain a repeated modifier");
}
// If not, add a new drop down
else
{
AddDropDown(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects);
}
}
// If last drop down and a modifier is selected but there are already 5 drop downs: warn the user
else if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]) && parent.Children().Size() >= 5)
{
// warn and reset the drop down
SetDropDownError(currentDropDown, warningMessage, L"Shortcut must contain an action key");
}
// If None is selected but it's the last index: warn
else if (keyCodeList[selectedKeyIndex] == 0)
{
// warn and reset the drop down
SetDropDownError(currentDropDown, warningMessage, L"Shortcut must contain an action key");
}
// If none of the above, then the action key will be set
}
// If it is the not the last drop down
else
{
if (KeyboardManagerHelper::IsModifierKey(keyCodeList[selectedKeyIndex]))
{
// If it matched any of the previous modifiers then reset that drop down
if (CheckRepeatedModifier(parent, dropDownIndex, selectedKeyIndex, keyCodeList))
{
// warn and reset the drop down
SetDropDownError(currentDropDown, warningMessage, L"Shortcut cannot contain a repeated modifier");
}
// If not, the modifier key will be set
}
// If None is selected and there are more than 2 drop downs
else if (keyCodeList[selectedKeyIndex] == 0 && parent.Children().Size() > 2)
{
// delete drop down
parent.Children().RemoveAt(dropDownIndex);
// delete drop down control object from the vector so that it can be destructed
keyDropDownControlObjects.erase(keyDropDownControlObjects.begin() + dropDownIndex);
parent.UpdateLayout();
}
else if (keyCodeList[selectedKeyIndex] == 0 && parent.Children().Size() <= 2)
{
// warn and reset the drop down
SetDropDownError(currentDropDown, warningMessage, L"Shortcut must have atleast 2 keys");
}
// If the user tries to set an action key check if all drop down menus after this are empty if it is not the first key
else if (dropDownIndex != 0)
{
bool isClear = true;
for (int i = dropDownIndex + 1; i < (int)parent.Children().Size(); i++)
{
ComboBox currentDropDown = parent.Children().GetAt(i).as<ComboBox>();
if (currentDropDown.SelectedIndex() != -1)
{
isClear = false;
break;
}
}

if (isClear)
{
// remove all the drop down
int elementsToBeRemoved = parent.Children().Size() - dropDownIndex - 1;
for (int i = 0; i < elementsToBeRemoved; i++)
{
parent.Children().RemoveAtEnd();
keyDropDownControlObjects.erase(keyDropDownControlObjects.end() - 1);
}
parent.UpdateLayout();
}
else
{
// warn and reset the drop down
SetDropDownError(currentDropDown, warningMessage, L"Shortcut cannot have more than one action key");
}
}
// If there an action key is chosen on the first drop down and there are more than one drop down menus
else
{
// warn and reset the drop down
SetDropDownError(currentDropDown, warningMessage, L"Shortcut must start with a modifier key");
}
}
}

// Reset the buffer based on the new selected drop down items
shortcutRemapBuffer[rowIndex][colIndex].SetKeyCodes(GetKeysFromStackPanel(parent));
}

// If the user searches for a key the selection handler gets invoked however if they click away it reverts back to the previous state. This can result in dangling references to added drop downs which were then reset.
// We handle this by removing the drop down if it no longer a child of the parent
for (long long i = keyDropDownControlObjects.size() - 1; i >= 0; i--)
{
uint32_t index;
bool found = parent.Children().IndexOf(keyDropDownControlObjects[i]->GetComboBox(), index);
if (!found)
{
keyDropDownControlObjects.erase(keyDropDownControlObjects.begin() + i);
}
}
});
}

// Function to set the selected index of the drop down
void KeyDropDownControl::SetSelectedIndex(int32_t index)
{
Expand All @@ -49,14 +221,15 @@ ComboBox KeyDropDownControl::GetComboBox()
}

// Function to add a drop down to the shortcut stack panel
void KeyDropDownControl::AddDropDown(StackPanel parent, const size_t rowIndex, const size_t colIndex, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects)
void KeyDropDownControl::AddDropDown(Grid table, StackPanel shortcutControl, StackPanel parent, const size_t colIndex, std::vector<std::vector<Shortcut>>& shortcutRemapBuffer, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects)
{
keyDropDownControlObjects.push_back(std::move(std::unique_ptr<KeyDropDownControl>(new KeyDropDownControl(rowIndex, colIndex, shortcutRemapBuffer, keyDropDownControlObjects, parent))));
keyDropDownControlObjects.push_back(std::move(std::unique_ptr<KeyDropDownControl>(new KeyDropDownControl(true))));
// Flyout to display the warning on the drop down element
Flyout warningFlyout;
TextBlock warningMessage;
warningFlyout.Content(warningMessage);
parent.Children().Append(keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->GetComboBox());
keyDropDownControlObjects[keyDropDownControlObjects.size() - 1]->SetSelectionHandler(table, shortcutControl, parent, colIndex, shortcutRemapBuffer, keyDropDownControlObjects);
parent.UpdateLayout();
}

Expand Down
Loading