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

Make Actions page editable #9949

Merged
6 commits merged into from
May 18, 2021
Merged

Make Actions page editable #9949

6 commits merged into from
May 18, 2021

Conversation

carlos-zamora
Copy link
Member

@carlos-zamora carlos-zamora commented Apr 26, 2021

Summary of the Pull Request

This PR lays the foundation for a new Actions page in the Settings UI as designed in #6900. The Actions page now leverages the ActionMap to display all of the key bindings and allow the user to modify the associated key chord or delete the key binding entirely.

References

#9621 - ActionMap
#9926 - ActionMap serialization
#9428 - ActionMap Spec
#6900 - Actions page
#9427 - Actions page design doc

Detailed Description of the Pull Request / Additional comments

Settings Model Changes

  • Command::Copy() now copies the ActionAndArgs
  • ActionMap::RebindKeys() handles changing the key chord of a key binding. If a conflict occurs, the conflicting key chord is overwritten.
  • ActionMap::DeleteKeyBinding() "deletes" a key binding by binding "unbound" to the given key chord.
  • ActionMap::KeyBindings() presents another view (similar to NameMap) of the ActionMap. It specifically presents a map of key chords to commands. It is generated similar to how NameMap is generated.

Editor Changes

  • Actions.xaml is mainly split into two parts:
    • ListView (as before) holds the list of key bindings. We could explore the idea of an items repeater, but the ListView seems to provide some niceties with regards to navigating the list via the key board (though none are selectable).
    • DataTemplate is used to represent each key binding inside the ListView. This is tricky because it is bound to a KeyBindingViewModel which must provide all context necessary to modify the UI and the settings model. We cannot use names to target UI elements inside this template, so we must make the view model smart and force updates to the UI via changes in the view model.
  • KeyBindingViewModel is a view model object that controls the UI and the settings model.

There are a number of TODOs in Actions.cpp will be long-term follow-ups and would be nice to have. This includes...

  • a binary search by name on Actions::KeyBindingList
  • presenting an error when the provided key chord is invalid.

Demo

Actions Page Demo

@github-actions

This comment has been minimized.

@carlos-zamora carlos-zamora force-pushed the dev/cazamor/actions-page/foundation branch from 9001102 to 4ccebc7 Compare May 5, 2021 04:26
@github-actions

This comment has been minimized.

Base automatically changed from dev/cazamor/tsm/action-map to main May 5, 2021 04:50
@carlos-zamora carlos-zamora force-pushed the dev/cazamor/actions-page/foundation branch from 4ccebc7 to 6460195 Compare May 5, 2021 04:53
@github-actions

This comment has been minimized.

@carlos-zamora carlos-zamora force-pushed the dev/cazamor/actions-page/foundation branch from 6460195 to 643b787 Compare May 8, 2021 01:19
@github-actions
Copy link

github-actions bot commented May 8, 2021

Misspellings found, please review:

  • Btn
  • Desctiption
  • seemlessly
  • whos
To accept these changes, run the following commands from this repository on this branch
pushd $(git rev-parse --show-toplevel)
perl -e '
my @expect_files=qw('".github/actions/spelling/expect/alphabet.txt
.github/actions/spelling/expect/expect.txt
.github/actions/spelling/expect/web.txt"');
@ARGV=@expect_files;
my @stale=qw('"actionmap reserialize Unregistering "');
my $re=join "|", @stale;
my $suffix=".".time();
my $previous="";
sub maybe_unlink { unlink($_[0]) if $_[0]; }
while (<>) {
  if ($ARGV ne $old_argv) { maybe_unlink($previous); $previous="$ARGV$suffix"; rename($ARGV, $previous); open(ARGV_OUT, ">$ARGV"); select(ARGV_OUT); $old_argv = $ARGV; }
  next if /^(?:$re)(?:(?:\r|\n)*$| .*)/; print;
}; maybe_unlink($previous);'
perl -e '
my $new_expect_file=".github/actions/spelling/expect/0e8d5f24c4cf80e104cf6d16ae004c54015c13ce.txt";
use File::Path qw(make_path);
make_path ".github/actions/spelling/expect";
open FILE, q{<}, $new_expect_file; chomp(my @words = <FILE>); close FILE;
my @add=qw('"Btn Desctiption seemlessly unregistering whos "');
my %items; @items{@words} = @words x (1); @items{@add} = @add x (1);
@words = sort {lc($a) cmp lc($b)} keys %items;
open FILE, q{>}, $new_expect_file; for my $word (@words) { print FILE "$word\n" if $word =~ /\w/; };
close FILE;'
popd
✏️ Contributor please read this

By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.

⚠️ The command is written for posix shells. You can copy the contents of each perl command excluding the outer ' marks and dropping any '"/"' quotation mark pairs into a file and then run perl file.pl from the root of the repository to run the code. Alternatively, you can manually insert the items...

If the listed items are:

  • ... misspelled, then please correct them instead of using the command.
  • ... names, please add them to .github/actions/spelling/dictionary/names.txt.
  • ... APIs, you can add them to a file in .github/actions/spelling/dictionary/.
  • ... just things you're using, please add them to an appropriate file in .github/actions/spelling/expect/.
  • ... tokens you only need in one place and shouldn't generally be used, you can add an item in an appropriate file in .github/actions/spelling/patterns/.

See the README.md in each directory for more information.

🔬 You can test your commits without appending to a PR by creating a new branch with that extra change and pushing it to your fork. The check-spelling action will run in response to your push -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. 😉

🗜️ If you see a bunch of garbage and it relates to a binary-ish string, please add a file path to the .github/actions/spelling/excludes.txt file instead of just accepting the garbage.

File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

@carlos-zamora carlos-zamora force-pushed the dev/cazamor/actions-page/foundation branch from 643b787 to ed9ec6d Compare May 10, 2021 18:05
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

Yea okay, I'm fine with like 99% of it, I just have a couple questions, and I think it needs more comments

src/cascadia/TerminalSettingsEditor/Actions.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsEditor/Actions.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsEditor/Actions.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalSettingsEditor/Actions.cpp Outdated Show resolved Hide resolved

Button acceptBTN{};
acceptBTN.Content(box_value(RS_(L"Actions_RenameConflictConfirmationAcceptButton")));
acceptBTN.Click([=](auto&, auto&) {
Copy link
Member

Choose a reason for hiding this comment

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

capturing everything by value here seems sus, but it's probably fine? The only things getting captured are already members of the SUI, and it's not like this will ever get triggered after the SUI is destroyed, so yea this is probably fine

src/cascadia/TerminalSettingsEditor/Actions.cpp Outdated Show resolved Hide resolved
Comment on lines +133 to +135
for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i)
{
const auto& kbdVM{ _KeyBindingList.GetAt(i) };
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i)
{
const auto& kbdVM{ _KeyBindingList.GetAt(i) };
for (const auto& kbdVM : _KeyBindingList)
{

right?

Copy link
Member

Choose a reason for hiding this comment

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

oh never mind, we do need the index below

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah. We could use ContainerFromItem, but I figured ContainerFromIndex would be faster here since we're already iterating over all of the key binding view models anyways.

@zadjii-msft zadjii-msft added Area-SettingsUI Anything specific to the SUI Product-Terminal The new Windows Terminal. labels May 12, 2021
@carlos-zamora
Copy link
Member Author

carlos-zamora commented May 12, 2021

Bug Bash Notes (5/12)

  • Performance
  • Layout and Style
    • Trash button is cut off
      • No Repro: attempted to repro on my surface book and main dev machine with different scaling.
    • make buttons the same height as the text box
    • make accept button have accent color
    • "ctrl+shift+w" has no right side padding to match the left side padding. 'W' is cut off
      • Won't Fix: this is just the style XAML has when right-aligning the text. Approved by Kayla.
  • Bugs
    • Move focus doesn't display direction
    • Narrator reads out "4 of 64" on the list item, instead of what we're focused on
    • Narrator says "button" for edit buttons
  • Features
  • Serialization

@carlos-zamora
Copy link
Member Author

Still need to come back to work on that performance issue. It seems doable, but I'll need a bit more help so I'll leave that for Monday.

Added a nice background to the list view item when it's in edit mode. An annoying problem is that it'll only appear if you're using the Windows OS theme. But that's due to #9716.

flyoutStack.Children().Append(confirmationQuestionTB);
flyoutStack.Children().Append(acceptBTN);

Flyout acceptChangesFlyout{};
Copy link
Contributor

Choose a reason for hiding this comment

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

Does a new flyout get created every time there's a conflicting command? Could there be a way to reuse it?

Copy link
Member Author

Choose a reason for hiding this comment

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

I wish! As mentioned in the comments below:

  1. The data template prevents us from getting a control by name. So we have no way of targeting the button and attaching a pre-saved flyout.
  2. The flyout needs to appear sometimes. So we can't just build it in XAML, because then we don't have control over when it appears or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see, dang that's pretty annoying 😢

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm unsure if this will work, but you might be able to give the button a flyout without having it automagically open the flyout on every button press by putting the flyout in FlyoutBase.AttachedFlyout. It's an attached property so this particular property can be set on just about any FrameworkElement, but IIRC it doesn't behave the same as if you set the Flyout property on Button.

https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.primitives.flyoutbase?view=winrt-19041#xaml-attached-properties - should tell you more about how to use this particular attached property

This is more an experimental idea though, not something I'd block over 😄

e.g.

<Button>
    <FlyoutBase.AttachedFlyout>
        <Flyout>
        </Flyout>
    </FlyoutBase.AttachedFlyout>
</Button>

src/cascadia/TerminalSettingsEditor/Actions.cpp Outdated Show resolved Hide resolved
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

Yea okay you touched everything I was worried about.

@carlos-zamora carlos-zamora added the AutoMerge Marked for automatic merge by the bot when requirements are met label May 18, 2021
@ghost
Copy link

ghost commented May 18, 2021

Hello @carlos-zamora!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit c66910b into main May 18, 2021
@ghost ghost deleted the dev/cazamor/actions-page/foundation branch May 18, 2021 21:37
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

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

Questions mostly around function rather than form. Form looks excellent, and I like how it feels.

};

[default_interface] runtimeclass ActionMap : IActionMapView
{
Boolean RebindKeys(Microsoft.Terminal.Control.KeyChord oldKeys, Microsoft.Terminal.Control.KeyChord newKeys);
Copy link
Member

Choose a reason for hiding this comment

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

i'm surprised that this takes the oldkeys and not the action -- shouldn't we enforce that the caller is providing an action?

Copy link
Member Author

Choose a reason for hiding this comment

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

So, oldKeys can be used as a primary key the way it's written now.

If we wanted to switch over to using ActionAndArgs, we could make the API look more like this:

void RegisterKeyBinding(ActionAndArgs, KeyChord);
void EraseKeyBinding(ActionAndArgs, KeyChord);

// For later...
void SetActionForKeyBinding(KeyChord, ActionAndArgs);
void SetName(ActionAndArgs, String);

I'm intentionally trying to make it so that we don't work with Command outside of ActionMap.

src/cascadia/TerminalSettingsEditor/Actions.h Show resolved Hide resolved
@ghost ghost added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label May 18, 2021
@ghost ghost removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label May 24, 2021
@ghost
Copy link

ghost commented May 25, 2021

🎉Windows Terminal Preview v1.9.1445.0 has been released which incorporates this pull request.:tada:

Handy links:

ghost pushed a commit that referenced this pull request Jun 3, 2021
Applies feedback from #9949 (review)

Highlights include:
- bugfix: make all edit buttons stay visible if the user is using assistive technology
- rename a few functions and resources to match the correct naming scheme
- update the localized text for a conflicting key chord being assigned
- provide better comments throughout the actions page code

## References
#9949 - Original PR
Closes #10168
carlos-zamora added a commit that referenced this pull request Jul 2, 2021
This introduces the ability to set the action for a key binding. A combo box is used to let the user select a new action.

## References
#6900 - Actions page Epic
#9427 - Actions page design doc
#9949 - Actions page PR

## Detailed Description of the Pull Request / Additional comments
### Settings Model Changes
- `ActionAndArgs`
   - new ctor that just takes a `ShortcutAction`
- `ActionMap`
   - `AvailableActions` provides a map of all the "acceptable" actions to choose from. This is a merged list of (1) all `{ "command": X }` style actions and (2) any actions with args that are already defined in the ActionMap (or any parents).
   - `RegisterKeyBinding` introduces a new unnamed key binding to the action map.

### Editor Changes
- XAML
   - Pretty straightforward, when in edit mode, we replace the text block with a combo box. This combo box just presents the actions you can choose from.
- `RebindKeysEventArgs` --> `ModifyKeyBindingEventArgs`
- `AvailableActionAndArgs`
   - stores the list of actions to choose from in the combo box
   - _Unfortunately_, `KeyBindingViewModel` needs this so that we can populate the combo box
   - `Actions` stores and maintains this though. We populate this from the settings model on navigation.
- `ProposedAction` vs `CurrentAction`
   - similar to `ProposedKeys` and `Keys`, we need a way to distinguish the value from the settings model and the value of the control (i.e. combo box).
   - `CurrentAction` --> settings model
   - `ProposedAction` --> combo box selected item

## Validation Steps Performed
- Cancel:
   - ✔️ change action --> cancel button --> begin editing action again --> original action is selected
- Accept:
   - ✔️ don't change anything
   - ✔️ change action --> OK! --> Save!
      - NOTE: The original action is still left as a stub `{ "command": "closePane" }`. This is intentional because we want to prevent all modifications to the command palette.
   - ✔️ change action & change key chord --> OK! --> Save!
   - ✔️ change action & change key chord (conflicting key chord) --> OK! --> click ok on flyout --> Save!
      - NOTE: original action is left as a stub; original key chord explicitly unbound; new command/keys combo added.
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-SettingsUI Anything specific to the SUI AutoMerge Marked for automatic merge by the bot when requirements are met Product-Terminal The new Windows Terminal.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants