Skip to content

Commit

Permalink
Add a simple page for keybindings (#9253)
Browse files Browse the repository at this point in the history
This was the only thing blocking me from signing off on #9224 in 1.7.

! CHANGE WARNING !
If we bind to `T.S.M.Command`s in XAML, then the compiler gets _very
angry_ at us. It generates two different versions of
`GetReferenceTypeMember_Icon` in `XamlTypeInfo.g.cpp`. Presumably
because there's an Icon on a NavViewItem and an Icon on a Command. We
don't really know why. Fortunately, the fix is "rename Command::Icon" to
"Command::IconPath". It's dumb, but it works. Thanks for the help with
that one Carlos ☺️ 

Unblocks #9224
  • Loading branch information
zadjii-msft committed Feb 23, 2021
1 parent 17c6f8e commit f87596f
Show file tree
Hide file tree
Showing 13 changed files with 446 additions and 78 deletions.
6 changes: 3 additions & 3 deletions src/cascadia/TerminalApp/ActionPaletteItem.cpp
Expand Up @@ -23,7 +23,7 @@ namespace winrt::TerminalApp::implementation
{
Name(command.Name());
KeyChordText(command.KeyChordText());
Icon(command.Icon());
Icon(command.IconPath());

_commandChangedRevoker = command.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& sender, auto& e) {
auto item{ weakThis.get() };
Expand All @@ -40,9 +40,9 @@ namespace winrt::TerminalApp::implementation
{
item->KeyChordText(senderCommand.KeyChordText());
}
else if (changedProperty == L"Icon")
else if (changedProperty == L"IconPath")
{
item->Icon(senderCommand.Icon());
item->Icon(senderCommand.IconPath());
}
}
});
Expand Down
22 changes: 14 additions & 8 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Expand Up @@ -93,21 +93,27 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget TerminalPage::SetSettings(CascadiaSettings settings, bool needRefreshUI)
{
_settings = settings;
if (needRefreshUI)
{
_RefreshUIForSettingsReload();
}

// Upon settings update we reload the system settings for scrolling as well.
// TODO: consider reloading this value periodically.
_systemRowsToScroll = _ReadSystemRowsToScroll();

auto weakThis{ get_weak() };
co_await winrt::resume_foreground(Dispatcher());
if (auto page{ weakThis.get() })
{
// Make sure to _UpdateCommandsForPalette before
// _RefreshUIForSettingsReload. _UpdateCommandsForPalette will make
// sure the KeyChordText of Commands is updated, which needs to
// happen before the Settings UI is reloaded and tries to re-read
// those values.
_UpdateCommandsForPalette();
CommandPalette().SetKeyMap(_settings.KeyMap());

if (needRefreshUI)
{
_RefreshUIForSettingsReload();
}

// Upon settings update we reload the system settings for scrolling as well.
// TODO: consider reloading this value periodically.
_systemRowsToScroll = _ReadSystemRowsToScroll();
}
}

Expand Down
61 changes: 61 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Actions.cpp
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"
#include "Actions.h"
#include "Actions.g.cpp"
#include "ActionsPageNavigationState.g.cpp"
#include "EnumEntry.h"

using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::System;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Microsoft::Terminal::Settings::Model;

namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
Actions::Actions()
{
InitializeComponent();

_filteredActions = winrt::single_threaded_observable_vector<winrt::Microsoft::Terminal::Settings::Model::Command>();
}

void Actions::OnNavigatedTo(const NavigationEventArgs& e)
{
_State = e.Parameter().as<Editor::ActionsPageNavigationState>();

for (const auto& [k, command] : _State.Settings().GlobalSettings().Commands())
{
// Filter out nested commands, and commands that aren't bound to a
// key. This page is currently just for displaying the actions that
// _are_ bound to keys.
if (command.HasNestedCommands() || command.KeyChordText().empty())
{
continue;
}
_filteredActions.Append(command);
}
}

Collections::IObservableVector<Command> Actions::FilteredActions()
{
return _filteredActions;
}

void Actions::_OpenSettingsClick(const IInspectable& /*sender*/,
const Windows::UI::Xaml::RoutedEventArgs& /*eventArgs*/)
{
const CoreWindow window = CoreWindow::GetForCurrentThread();
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);

const auto target = altPressed ? SettingsTarget::DefaultsFile : SettingsTarget::SettingsFile;

_State.RequestOpenJson(target);
}

}
49 changes: 49 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Actions.h
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#pragma once

#include "Actions.g.h"
#include "ActionsPageNavigationState.g.h"
#include "Utils.h"

namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct ActionsPageNavigationState : ActionsPageNavigationStateT<ActionsPageNavigationState>
{
public:
ActionsPageNavigationState(const Model::CascadiaSettings& settings) :
_Settings{ settings } {}

void RequestOpenJson(const Model::SettingsTarget target)
{
_OpenJsonHandlers(nullptr, target);
}

GETSET_PROPERTY(Model::CascadiaSettings, Settings, nullptr)
TYPED_EVENT(OpenJson, Windows::Foundation::IInspectable, Model::SettingsTarget);
};

struct Actions : ActionsT<Actions>
{
public:
Actions();

void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);

Windows::Foundation::Collections::IObservableVector<winrt::Microsoft::Terminal::Settings::Model::Command> FilteredActions();

GETSET_PROPERTY(Editor::ActionsPageNavigationState, State, nullptr);

private:
friend struct ActionsT<Actions>; // for Xaml to bind events
Windows::Foundation::Collections::IObservableVector<winrt::Microsoft::Terminal::Settings::Model::Command> _filteredActions{ nullptr };

void _OpenSettingsClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
};
}

namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(Actions);
}
23 changes: 23 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Actions.idl
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import "EnumEntry.idl";

namespace Microsoft.Terminal.Settings.Editor
{
runtimeclass ActionsPageNavigationState
{
Microsoft.Terminal.Settings.Model.CascadiaSettings Settings;
void RequestOpenJson(Microsoft.Terminal.Settings.Model.SettingsTarget target);
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Settings.Model.SettingsTarget> OpenJson;
};

[default_interface] runtimeclass Actions : Windows.UI.Xaml.Controls.Page
{
Actions();
ActionsPageNavigationState State { get; };

IObservableVector<Microsoft.Terminal.Settings.Model.Command> FilteredActions { get; };

}
}
190 changes: 190 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Actions.xaml
@@ -0,0 +1,190 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information. -->
<Page
x:Class="Microsoft.Terminal.Settings.Editor.Actions"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:SettingsModel="using:Microsoft.Terminal.Settings.Model"
mc:Ignorable="d">

<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="CommonResources.xaml"/>
</ResourceDictionary.MergedDictionaries>

<local:StringIsEmptyConverter x:Key="CommandKeyChordVisibilityConverter"/>

<!-- Template for actions. This is _heavily_ copied from the command
palette, with modifications:
* We don't need to use a HighlightedTextControl, because we're
not filtering this list
* We don't need the chevron for nested commands
* We're not displaying the icon
* We're binding directly to a Command, not a FilteredCommand
If we wanted to reuse the command palette's list more directly,
that's theoretically possible, but then it would need to be
lifted out of TerminalApp and either moved into the
TerminalSettingsEditor or moved to it's own project consumed by
both TSE and TerminalApp.
-->
<DataTemplate x:Key="GeneralItemTemplate" x:DataType="SettingsModel:Command">

<!-- This HorizontalContentAlignment="Stretch" is important
to make sure it takes the entire width of the line -->
<ListViewItem HorizontalContentAlignment="Stretch"
AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
AutomationProperties.AcceleratorKey="{x:Bind KeyChordText, Mode=OneWay}">

<Grid HorizontalAlignment="Stretch" ColumnSpacing="8" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<!-- icon -->
<ColumnDefinition Width="Auto"/>
<!-- command label -->
<ColumnDefinition Width="*"/>
<!-- key chord -->
<ColumnDefinition Width="32"/>
<!-- gutter for scrollbar -->
</Grid.ColumnDefinitions>

<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
Text="{x:Bind Name, Mode=OneWay}"/>

<!-- The block for the key chord is only visible
when there's actual text set as the label. See
CommandKeyChordVisibilityConverter for details.
Inexplicably, we don't need to set the
AutomationProperties to Raw here, unlike in the
CommandPalette. We're not quite sure why.-->
<Border Grid.Column="2"
Visibility="{x:Bind KeyChordText,
Mode=OneWay,
Converter={StaticResource CommandKeyChordVisibilityConverter}}"
Style="{ThemeResource KeyChordBorderStyle}"
Padding="2,0,2,0"
HorizontalAlignment="Right"
VerticalAlignment="Center">

<TextBlock Style="{ThemeResource KeyChordTextBlockStyle}"
FontSize="12"
Text="{x:Bind KeyChordText, Mode=OneWay}" />
</Border>
</Grid>
</ListViewItem>
</DataTemplate>

<!-- These resources again, HEAVILY copied from the command palette -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<!-- TextBox colors !-->
<SolidColorBrush x:Key="TextControlBackground" Color="#333333"/>
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#B5B5B5"/>
<SolidColorBrush x:Key="TextControlForeground" Color="#B5B5B5"/>
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#404040"/>
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#B5B5B5"/>

<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#404040"/>
<SolidColorBrush x:Key="TextControlForegroundPointerOver" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#404040"/>
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>

<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#333333"/>
<SolidColorBrush x:Key="TextControlForegroundFocused" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#404040"/>
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>

<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle" TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>

</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<!-- TextBox colors !-->
<SolidColorBrush x:Key="TextControlBackground" Color="#CCCCCC"/>
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#636363"/>
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#636363"/>
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#636363"/>

<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#DADADA"/>
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#636363"/>
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>

<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#CCCCCC"/>
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#636363"/>
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>

<!-- KeyChordText styles -->
<Style x:Key="KeyChordBorderStyle" TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>

</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">

<!-- KeyChordText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="KeyChordBorderStyle" TargetType="Border"/>
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock"/>

</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>


</ResourceDictionary>
</Page.Resources>

<ScrollViewer>
<StackPanel Style="{StaticResource SettingsStackStyle}">
<TextBlock x:Uid="Globals_KeybindingsDisclaimer"
Style="{StaticResource DisclaimerStyle}"/>

<!-- The Nav_OpenJSON resource just so happens to have a .Content
and .Tooltip that are _exactly_ what we're looking for here. -->

<HyperlinkButton x:Uid="Nav_OpenJSON"
Click="_OpenSettingsClick" />

<!-- Keybindings -->

<!-- NOTE: Globals_Keybindings.Header is not defined, because that
would result in the page having "Keybindings" displayed twice, which
looks quite redundant -->
<ContentPresenter x:Uid="Globals_Keybindings" Margin="0">

<ListView HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SelectionMode="None"
IsItemClickEnabled="False"
CanReorderItems="False"
AllowDrop="False"
ItemsSource="{x:Bind FilteredActions, Mode=OneWay}"
ItemTemplate="{StaticResource GeneralItemTemplate}">
</ListView>

</ContentPresenter>

</StackPanel>

</ScrollViewer>
</Page>

0 comments on commit f87596f

Please sign in to comment.