Skip to content

Commit

Permalink
Add support for a right-click context menu (#14775)
Browse files Browse the repository at this point in the history
Experimental for now. `experimental.rightClickContextMenu`, a
per-profile setting. Long term we want to enable full mouse bindings, at
which point this would be replaced.

Closes #3337

This adds **two** context menus to the `TermControl` - one for
right-clicking with a selection, and one without. The implementation is
designed to follows the API experience of the context menu on something
like a [`RichEditBox`](winui2gallery://item/RichEditBox). The hosting
application adds a handler for the menu's `Opening` event, and appends
whatever items it wants at that time.

So `TermControl` only implements a few "actions" by default - copy,
past, find. `TerminalApp` is then responsible for adding anything else
it needs. Right now, those actions are:
* Duplicate tab
* Duplicate pane
* Close Tab
* Close pane

Screenshots in
#14775 (comment)
  • Loading branch information
zadjii-msft authored Mar 17, 2023
1 parent 4ca1962 commit 7383b26
Show file tree
Hide file tree
Showing 26 changed files with 322 additions and 21 deletions.
3 changes: 0 additions & 3 deletions src/cascadia/TerminalApp/ColorPickupFlyout.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#include "pch.h"
#include "ColorPickupFlyout.h"
#include "ColorPickupFlyout.g.cpp"
#include "winrt/Windows.UI.Xaml.Media.h"
#include "winrt/Windows.UI.Xaml.Shapes.h"
#include "winrt/Windows.UI.Xaml.Interop.h"
#include <LibraryResources.h>

namespace winrt::TerminalApp::implementation
Expand Down
14 changes: 10 additions & 4 deletions src/cascadia/TerminalApp/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@
<data name="TabClose" xml:space="preserve">
<value>Close Tab</value>
</data>
<data name="PaneClose" xml:space="preserve">
<value>Close Pane</value>
</data>
<data name="SplitTabText" xml:space="preserve">
<value>Split Tab</value>
</data>
<data name="SplitPaneText" xml:space="preserve">
<value>Split Pane</value>
</data>
<data name="TabColorChoose" xml:space="preserve">
<value>Color...</value>
</data>
Expand Down Expand Up @@ -708,9 +717,6 @@
<data name="DropPathTabRun.Text" xml:space="preserve">
<value>Open a new tab in given starting directory</value>
</data>
<data name="SplitTabText" xml:space="preserve">
<value>Split Tab</value>
</data>
<data name="DropPathTabNewWindow.Text" xml:space="preserve">
<value>Open a new window with given starting directory</value>
</data>
Expand Down Expand Up @@ -795,4 +801,4 @@
<data name="NewTabMenuFolderEmpty" xml:space="preserve">
<value>Empty...</value>
</data>
</root>
</root>
76 changes: 76 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1618,6 +1618,9 @@ namespace winrt::TerminalApp::implementation
});

term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });

term.ContextMenu().Opening({ this, &TerminalPage::_ContextMenuOpened });
term.SelectionContextMenu().Opening({ this, &TerminalPage::_SelectionMenuOpened });
}

// Method Description:
Expand Down Expand Up @@ -4320,6 +4323,78 @@ namespace winrt::TerminalApp::implementation
_updateThemeColors();
}

void TerminalPage::_ContextMenuOpened(const IInspectable& sender,
const IInspectable& /*args*/)
{
_PopulateContextMenu(sender, false /*withSelection*/);
}
void TerminalPage::_SelectionMenuOpened(const IInspectable& sender,
const IInspectable& /*args*/)
{
_PopulateContextMenu(sender, true /*withSelection*/);
}

void TerminalPage::_PopulateContextMenu(const IInspectable& sender,
const bool /*withSelection*/)
{
// withSelection can be used to add actions that only appear if there's
// selected text, like "search the web". In this initial draft, it's not
// actually augmented by the TerminalPage, so it's left commented out.

const auto& menu{ sender.try_as<MUX::Controls::CommandBarFlyout>() };
if (!menu)
{
return;
}

// Helper lambda for dispatching an ActionAndArgs onto the
// ShortcutActionDispatch. Used below to wire up each menu entry to the
// respective action.

auto weak = get_weak();
auto makeCallback = [weak](const ActionAndArgs& actionAndArgs) {
return [weak, actionAndArgs](auto&&, auto&&) {
if (auto page{ weak.get() })
{
page->_actionDispatch->DoAction(actionAndArgs);
}
};
};

auto makeItem = [&menu, &makeCallback](const winrt::hstring& label,
const winrt::hstring& icon,
const auto& action) {
AppBarButton button{};

if (!icon.empty())
{
auto iconElement = IconPathConverter::IconWUX(icon);
Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
button.Icon(iconElement);
}

button.Label(label);
button.Click(makeCallback(action));
menu.SecondaryCommands().Append(button);
};

// Wire up each item to the action that should be performed. By actually
// connecting these to actions, we ensure the implementation is
// consistent. This also leaves room for customizing this menu with
// actions in the future.

makeItem(RS_(L"SplitPaneText"), L"\xF246", ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate } });
makeItem(RS_(L"DuplicateTabText"), L"\xF5ED", ActionAndArgs{ ShortcutAction::DuplicateTab, nullptr });

// Only wire up "Close Pane" if there's multiple panes.
if (_GetFocusedTabImpl()->GetLeafPaneCount() > 1)
{
makeItem(RS_(L"PaneClose"), L"\xE89F", ActionAndArgs{ ShortcutAction::ClosePane, nullptr });
}

makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } });
}

// Handler for our WindowProperties's PropertyChanged event. We'll use this
// to pop the "Identify Window" toast when the user renames our window.
winrt::fire_and_forget TerminalPage::_windowPropertyChanged(const IInspectable& /*sender*/,
Expand All @@ -4343,4 +4418,5 @@ namespace winrt::TerminalApp::implementation
}
}
}

}
4 changes: 4 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,10 @@ namespace winrt::TerminalApp::implementation
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);

void _ContextMenuOpened(const IInspectable& sender, const IInspectable& args);
void _SelectionMenuOpened(const IInspectable& sender, const IInspectable& args);
void _PopulateContextMenu(const IInspectable& sender, const bool withSelection);

#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
#define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);
Expand Down
25 changes: 17 additions & 8 deletions src/cascadia/TerminalControl/ControlInteractivity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// GH#9396: we prioritize hyper-link over VT mouse events
auto hyperlink = _core->GetHyperlink(terminalPosition.to_core_point());
if (WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown) &&
ctrlEnabled && !hyperlink.empty())
ctrlEnabled &&
!hyperlink.empty())
{
const auto clickCount = _numberOfClicks(pixelPosition, timestamp);
// Handle hyper-link only on the first click to prevent multiple activations
Expand Down Expand Up @@ -255,14 +256,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
else if (WI_IsFlagSet(buttonState, MouseButtonState::IsRightButtonDown))
{
// Try to copy the text and clear the selection
const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, nullptr);
_core->ClearSelection();
if (_core->CopyOnSelect() || !successfulCopy)
if (_core->Settings().RightClickContextMenu())
{
// CopyOnSelect: right click always pastes!
// Otherwise: no selection --> paste
RequestPasteTextFromClipboard();
auto contextArgs = winrt::make<ContextMenuRequestedEventArgs>(til::point{ pixelPosition }.to_winrt_point());
_ContextMenuRequestedHandlers(*this, contextArgs);
}
else
{
// Try to copy the text and clear the selection
const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, nullptr);
_core->ClearSelection();
if (_core->CopyOnSelect() || !successfulCopy)
{
// CopyOnSelect: right click always pastes!
// Otherwise: no selection --> paste
RequestPasteTextFromClipboard();
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/ControlInteractivity.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
TYPED_EVENT(ContextMenuRequested, IInspectable, Control::ContextMenuRequestedEventArgs);

private:
// NOTE: _uiaEngine must be ordered before _core.
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalControl/ControlInteractivity.idl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;

// Used to communicate to the TermControl, but not necessarily higher up in the stack
event Windows.Foundation.TypedEventHandler<Object, ContextMenuRequestedEventArgs> ContextMenuRequested;


};
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/EventArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "EventArgs.h"
#include "TitleChangedEventArgs.g.cpp"
#include "CopyToClipboardEventArgs.g.cpp"
#include "ContextMenuRequestedEventArgs.g.cpp"
#include "PasteFromClipboardEventArgs.g.cpp"
#include "OpenHyperlinkEventArgs.g.cpp"
#include "NoticeEventArgs.g.cpp"
Expand Down
10 changes: 10 additions & 0 deletions src/cascadia/TerminalControl/EventArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "TitleChangedEventArgs.g.h"
#include "CopyToClipboardEventArgs.g.h"
#include "ContextMenuRequestedEventArgs.g.h"
#include "PasteFromClipboardEventArgs.g.h"
#include "OpenHyperlinkEventArgs.g.h"
#include "NoticeEventArgs.g.h"
Expand Down Expand Up @@ -53,6 +54,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Windows::Foundation::IReference<CopyFormat> _formats;
};

struct ContextMenuRequestedEventArgs : public ContextMenuRequestedEventArgsT<ContextMenuRequestedEventArgs>
{
public:
ContextMenuRequestedEventArgs(winrt::Windows::Foundation::Point pos) :
_Position(pos) {}

WINRT_PROPERTY(winrt::Windows::Foundation::Point, Position);
};

struct PasteFromClipboardEventArgs : public PasteFromClipboardEventArgsT<PasteFromClipboardEventArgs>
{
public:
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalControl/EventArgs.idl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ namespace Microsoft.Terminal.Control
Windows.Foundation.IReference<CopyFormat> Formats { get; };
}

runtimeclass ContextMenuRequestedEventArgs
{
Windows.Foundation.Point Position { get; };
}

runtimeclass TitleChangedEventArgs
{
String Title;
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/IControlSettings.idl
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@ namespace Microsoft.Terminal.Control
Boolean SoftwareRendering { get; };
Boolean ShowMarks { get; };
Boolean UseBackgroundImageForWindow { get; };
Boolean RightClickContextMenu { get; };
};
}
34 changes: 33 additions & 1 deletion src/cascadia/TerminalControl/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,36 @@ Please either install the missing font or choose another one.</value>
<value>No results found</value>
<comment>Announced to a screen reader when the user searches for some text and there are no matches for that text in the terminal.</comment>
</data>
</root>
<data name="PasteCommandButton.Label" xml:space="preserve">
<value>Paste</value>
<comment>The label of a button for pasting the contents of the clipboard.</comment>
</data>
<data name="PasteCommandButton.ToolTipService.ToolTip" xml:space="preserve">
<value>Paste</value>
<comment>The tooltip for a paste button</comment>
</data>
<data name="CopyCommandButton.Label" xml:space="preserve">
<value>Copy</value>
<comment>The label of a button for copying the selected text to the clipboard.</comment>
</data>
<data name="CopyCommandButton.ToolTipService.ToolTip" xml:space="preserve">
<value>Copy</value>
<comment>The tooltip for a copy button</comment>
</data>
<data name="PasteWithSelectionCommandButton.Label" xml:space="preserve">
<value>Paste</value>
<comment>The label of a button for pasting the contents of the clipboard.</comment>
</data>
<data name="PasteWithSelectionCommandButton.ToolTipService.ToolTip" xml:space="preserve">
<value>Paste</value>
<comment>The tooltip for a paste button</comment>
</data>
<data name="SearchCommandButton.Label" xml:space="preserve">
<value>Find...</value>
<comment>The label of a button for searching for the selected text</comment>
</data>
<data name="SearchCommandButton.ToolTipService.ToolTip" xml:space="preserve">
<value>Find</value>
<comment>The tooltip for a button for searching for the selected text</comment>
</data>
</root>
2 changes: 0 additions & 2 deletions src/cascadia/TerminalControl/SearchBoxControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ Author(s):
--*/

#pragma once
#include "winrt/Windows.UI.Xaml.h"
#include "winrt/Windows.UI.Xaml.Controls.h"

#include "SearchBoxControl.g.h"

Expand Down
Loading

0 comments on commit 7383b26

Please sign in to comment.