mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Implement custom text context menus to fix crashes (#18854)
This works around a bug in WinUI where it creates a single context menu/flyout for text elements per thread, not per `XamlRoot`, similar to many other areas. Since the `XamlRoot` cannot change after creation, this means that once you've opened the flyout, you're locked into that window (= XAML root) forever. You can't open the flyout in another window and once you've closed that window, you can't open it anywhere at all. Closes #18599 * Flies out right click in the * About dialog ✅ * Search dialog ✅ * Word delimiters setting ✅ * Launch size setting ✅ * Across two windows ✅ (cherry picked from commit 076746a7a62197111981ca706eac9ff948286133) Service-Card-Id: PVTI_lADOAF3p4s4Axadtzgd5l-U Service-Version: 1.23
This commit is contained in:
parent
59ff525128
commit
eae5fbd83d
3
.github/actions/spelling/expect/expect.txt
vendored
3
.github/actions/spelling/expect/expect.txt
vendored
@ -128,6 +128,7 @@ Blt
|
||||
blu
|
||||
BLUESCROLL
|
||||
bmi
|
||||
bodgy
|
||||
BODGY
|
||||
BOLDFONT
|
||||
Borland
|
||||
@ -371,8 +372,8 @@ Dcd
|
||||
DColor
|
||||
dcommon
|
||||
DComposition
|
||||
dde
|
||||
DDDCCC
|
||||
dde
|
||||
DDESHARE
|
||||
DDevice
|
||||
DEADCHAR
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
x:Uid="AboutDialog"
|
||||
DefaultButton="Close"
|
||||
@ -17,6 +18,9 @@
|
||||
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock IsTextSelectionEnabled="True">
|
||||
<TextBlock.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBlock.ContextFlyout>
|
||||
<Run AutomationProperties.HeadingLevel="1"
|
||||
Text="{x:Bind ApplicationDisplayName}" /> <LineBreak />
|
||||
<Run x:Uid="AboutDialog_VersionLabel" />
|
||||
@ -39,7 +43,7 @@
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Visibility="{x:Bind UpdatesAvailable, Mode=OneWay}">
|
||||
<TextBlock IsTextSelectionEnabled="False">
|
||||
<TextBlock>
|
||||
<Run x:Uid="AboutDialog_UpdateAvailableLabel" />
|
||||
</TextBlock>
|
||||
<!-- <Button x:Uid="AboutDialog_InstallUpdateButton"
|
||||
@ -59,4 +63,3 @@
|
||||
Click="_ThirdPartyNoticesOnClick" />
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
|
||||
|
||||
@ -284,7 +284,11 @@
|
||||
IsSpellCheckEnabled="False"
|
||||
PlaceholderText="{x:Bind SearchBoxPlaceholderText, Mode=OneWay}"
|
||||
Text=""
|
||||
TextChanged="_filterTextChanged" />
|
||||
TextChanged="_filterTextChanged">
|
||||
<TextBox.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBox.ContextFlyout>
|
||||
</TextBox>
|
||||
|
||||
<TextBlock x:Name="_prefixCharacter"
|
||||
Grid.Row="0"
|
||||
|
||||
@ -78,6 +78,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void MarkdownPaneContent::_loadText()
|
||||
{
|
||||
auto block = WUX::Controls::TextBlock();
|
||||
block.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{});
|
||||
block.IsTextSelectionEnabled(true);
|
||||
block.FontFamily(WUX::Media::FontFamily{ L"Cascadia Code" });
|
||||
block.Text(FileContents());
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@ -51,7 +52,11 @@
|
||||
Grid.Column="0"
|
||||
Margin="4"
|
||||
PlaceholderText="Enter a path to a markdown file..."
|
||||
Text="Z:\dev\simple-test.md" />
|
||||
Text="Z:\dev\simple-test.md">
|
||||
<TextBox.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBox.ContextFlyout>
|
||||
</TextBox>
|
||||
<StackPanel Grid.Column="1"
|
||||
Orientation="Horizontal">
|
||||
<Button Margin="4"
|
||||
@ -105,7 +110,11 @@
|
||||
FontFamily="Cascadia Code"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{x:Bind FileContents, Mode=TwoWay}"
|
||||
Visibility="{x:Bind Editing}" />
|
||||
Visibility="{x:Bind Editing}">
|
||||
<TextBox.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBox.ContextFlyout>
|
||||
</TextBox>
|
||||
|
||||
<ScrollViewer x:Name="_scrollViewer"
|
||||
Grid.Column="1"
|
||||
|
||||
@ -20,6 +20,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_root.Background(bg.try_as<Media::Brush>());
|
||||
|
||||
_box = winrt::Windows::UI::Xaml::Controls::TextBox{};
|
||||
_box.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{});
|
||||
_box.Margin({ 10, 10, 10, 10 });
|
||||
_box.AcceptsReturn(true);
|
||||
_box.TextWrapping(TextWrapping::Wrap);
|
||||
|
||||
@ -182,7 +182,6 @@
|
||||
Grid.Column="1"
|
||||
Margin="12,0,12,6"
|
||||
FontFamily="Cascadia Mono, Consolas"
|
||||
IsTextSelectionEnabled="False"
|
||||
Style="{ThemeResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind Input}"
|
||||
Visibility="{Binding ElementName=rootItem, Path=IsSelected}" />
|
||||
@ -263,7 +262,11 @@
|
||||
Margin="8,0,8,8"
|
||||
AllowFocusOnInteraction="True"
|
||||
TextChanged="_filterTextChanged"
|
||||
Visibility="{x:Bind HasSnippets, Mode=OneWay}" />
|
||||
Visibility="{x:Bind HasSnippets, Mode=OneWay}">
|
||||
<TextBox.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBox.ContextFlyout>
|
||||
</TextBox>
|
||||
|
||||
<mux:TreeView x:Name="_treeView"
|
||||
Grid.Row="2"
|
||||
|
||||
@ -141,7 +141,11 @@
|
||||
PlaceholderText="{x:Bind SearchBoxPlaceholderText, Mode=OneWay}"
|
||||
Text=""
|
||||
TextChanged="_filterTextChanged"
|
||||
Visibility="Collapsed" />
|
||||
Visibility="Collapsed">
|
||||
<TextBox.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBox.ContextFlyout>
|
||||
</TextBox>
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
Margin="8,0,8,8"
|
||||
@ -224,7 +228,11 @@
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
IsTextSelectionEnabled="True"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
TextWrapping="WrapWholeWords">
|
||||
<TextBlock.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBlock.ContextFlyout>
|
||||
</TextBlock>
|
||||
<ScrollViewer MaxHeight="64"
|
||||
VerticalScrollBarVisibility="Visible"
|
||||
VerticalScrollMode="Enabled"
|
||||
@ -232,7 +240,11 @@
|
||||
<TextBlock x:Name="_descriptionComment"
|
||||
Margin="0,0,20,0"
|
||||
IsTextSelectionEnabled="True"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
TextWrapping="WrapWholeWords">
|
||||
<TextBlock.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBlock.ContextFlyout>
|
||||
</TextBlock>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
MinHeight="16"
|
||||
mc:Ignorable="d">
|
||||
@ -67,6 +68,10 @@
|
||||
IsSpellCheckEnabled="False"
|
||||
LostFocus="RenameBoxLostFocusHandler"
|
||||
MaxLength="1024"
|
||||
Visibility="Collapsed" />
|
||||
Visibility="Collapsed">
|
||||
<TextBox.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBox.ContextFlyout>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@ -70,8 +70,7 @@
|
||||
FontSize="12">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Placement="Mouse">
|
||||
<TextBlock IsTextSelectionEnabled="False"
|
||||
TextWrapping="Wrap">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run x:Uid="NewTabRun" /> <LineBreak />
|
||||
<Run x:Uid="NewPaneRun"
|
||||
FontStyle="Italic" /> <LineBreak />
|
||||
|
||||
@ -137,6 +137,9 @@
|
||||
DefaultButton="Primary">
|
||||
<TextBlock IsTextSelectionEnabled="True"
|
||||
TextWrapping="WrapWholeWords">
|
||||
<TextBlock.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBlock.ContextFlyout>
|
||||
<Run x:Name="NoticeMessage" />
|
||||
</TextBlock>
|
||||
</ContentDialog>
|
||||
@ -148,6 +151,9 @@
|
||||
DefaultButton="Primary">
|
||||
<TextBlock IsTextSelectionEnabled="True"
|
||||
TextWrapping="WrapWholeWords">
|
||||
<TextBlock.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBlock.ContextFlyout>
|
||||
<Run x:Name="CouldNotOpenUriReason" /> <LineBreak />
|
||||
<Run x:Name="UnopenedUri"
|
||||
FontFamily="Cascadia Mono" />
|
||||
@ -191,7 +197,11 @@
|
||||
<TextBox x:Name="WindowRenamerTextBox"
|
||||
KeyDown="_WindowRenamerKeyDown"
|
||||
KeyUp="_WindowRenamerKeyUp"
|
||||
Text="{x:Bind WindowProperties.WindowName, Mode=OneWay}" />
|
||||
Text="{x:Bind WindowProperties.WindowName, Mode=OneWay}">
|
||||
<TextBox.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBox.ContextFlyout>
|
||||
</TextBox>
|
||||
</mux:TeachingTip.Content>
|
||||
</mux:TeachingTip>
|
||||
|
||||
|
||||
@ -419,6 +419,7 @@ namespace winrt::TerminalApp::implementation
|
||||
auto buttonText = RS_(L"Ok");
|
||||
|
||||
Controls::TextBlock warningsTextBlock;
|
||||
warningsTextBlock.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{});
|
||||
// Make sure you can copy-paste
|
||||
warningsTextBlock.IsTextSelectionEnabled(true);
|
||||
// Make sure the lines of text wrap
|
||||
@ -468,6 +469,7 @@ namespace winrt::TerminalApp::implementation
|
||||
auto buttonText = RS_(L"Ok");
|
||||
|
||||
Controls::TextBlock warningsTextBlock;
|
||||
warningsTextBlock.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{});
|
||||
// Make sure you can copy-paste
|
||||
warningsTextBlock.IsTextSelectionEnabled(true);
|
||||
// Make sure the lines of text wrap
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.Terminal.Control"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
x:Name="Root"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
@ -197,7 +198,11 @@
|
||||
VerticalAlignment="Center"
|
||||
IsSpellCheckEnabled="False"
|
||||
KeyDown="TextBoxKeyDown"
|
||||
TextChanged="TextBoxTextChanged" />
|
||||
TextChanged="TextBoxTextChanged">
|
||||
<TextBox.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBox.ContextFlyout>
|
||||
</TextBox>
|
||||
|
||||
<TextBlock x:Name="StatusBox"
|
||||
x:Uid="SearchBox_StatusBox"
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.Terminal.Control"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
@ -1272,6 +1273,9 @@
|
||||
Placement="Mouse">
|
||||
<TextBlock IsTextSelectionEnabled="True"
|
||||
TextWrapping="Wrap">
|
||||
<TextBlock.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBlock.ContextFlyout>
|
||||
<Run x:Name="HoveredUri" /> <LineBreak />
|
||||
<Run x:Uid="HowToOpenRun"
|
||||
FontStyle="Italic" />
|
||||
|
||||
@ -132,6 +132,11 @@
|
||||
<Style x:Key="TextBoxSettingStyle"
|
||||
BasedOn="{StaticResource DefaultTextBoxStyle}"
|
||||
TargetType="TextBox">
|
||||
<Setter Property="ContextFlyout">
|
||||
<Setter.Value>
|
||||
<mtu:TextMenuFlyout />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="MinWidth" Value="{StaticResource StandardBoxMinWidth}" />
|
||||
<Setter Property="MaxWidth" Value="400" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
|
||||
136
src/cascadia/UIHelpers/Resources/en-US/Resources.resw
Normal file
136
src/cascadia/UIHelpers/Resources/en-US/Resources.resw
Normal file
@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Cut" xml:space="preserve">
|
||||
<value>Cut</value>
|
||||
<comment>"Cut" as contained in a Cut/Copy/Paste menu</comment>
|
||||
</data>
|
||||
<data name="Copy" xml:space="preserve">
|
||||
<value>Copy</value>
|
||||
<comment>"Copy" as contained in a Cut/Copy/Paste menu</comment>
|
||||
</data>
|
||||
<data name="Paste" xml:space="preserve">
|
||||
<value>Paste</value>
|
||||
<comment>"Paste" as contained in a Cut/Copy/Paste menu</comment>
|
||||
</data>
|
||||
<data name="SelectAll" xml:space="preserve">
|
||||
<value>Select All</value>
|
||||
<comment>"Select All" as contained in a Cut/Copy/Paste menu</comment>
|
||||
</data>
|
||||
</root>
|
||||
204
src/cascadia/UIHelpers/TextMenuFlyout.cpp
Normal file
204
src/cascadia/UIHelpers/TextMenuFlyout.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "TextMenuFlyout.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "TextMenuFlyout.g.cpp"
|
||||
|
||||
using namespace ::winrt::Windows::UI::Xaml;
|
||||
using namespace ::winrt::Windows::UI::Xaml::Controls;
|
||||
using namespace ::winrt::Windows::ApplicationModel::Resources::Core;
|
||||
using namespace ::winrt::Microsoft::UI::Xaml::Controls;
|
||||
using namespace ::winrt::Windows::System;
|
||||
using namespace ::winrt::Windows::UI::Xaml::Input;
|
||||
|
||||
using MenuFlyoutItemClick = void (*)(IInspectable const&, RoutedEventArgs const&);
|
||||
|
||||
constexpr auto NullSymbol = static_cast<Symbol>(0);
|
||||
|
||||
namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
{
|
||||
#pragma warning(suppress : 26455) // Default constructor should not throw. Declare it 'noexcept' (f.6).
|
||||
TextMenuFlyout::TextMenuFlyout()
|
||||
{
|
||||
// Most of the initialization is delayed until the first call to MenuFlyout_Opening.
|
||||
Opening({ this, &TextMenuFlyout::MenuFlyout_Opening });
|
||||
}
|
||||
|
||||
void TextMenuFlyout::MenuFlyout_Opening(IInspectable const&, IInspectable const&)
|
||||
{
|
||||
auto target = Target();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
hstring selection;
|
||||
bool writable = false;
|
||||
|
||||
// > Common group of selectable controls with common actions
|
||||
// > The I in MIDL stands for...
|
||||
// No common interface.
|
||||
if (const auto box = target.try_as<NumberBox>())
|
||||
{
|
||||
target = box.GetTemplateChild(L"InputBox").as<TextBox>();
|
||||
}
|
||||
if (const auto control = target.try_as<TextBlock>())
|
||||
{
|
||||
selection = control.SelectedText();
|
||||
}
|
||||
else if (const auto control = target.try_as<TextBox>())
|
||||
{
|
||||
selection = control.SelectedText();
|
||||
writable = true;
|
||||
}
|
||||
else if (const auto control = target.try_as<RichTextBlock>())
|
||||
{
|
||||
selection = control.SelectedText();
|
||||
}
|
||||
|
||||
if (!_copy)
|
||||
{
|
||||
til::small_vector<MenuFlyoutItemBase, 4> items;
|
||||
|
||||
if (writable)
|
||||
{
|
||||
_cut = items.emplace_back(_createMenuItem(Symbol::Cut, RS_(L"Cut"), { this, &TextMenuFlyout::Cut_Click }, VirtualKeyModifiers::Control, VirtualKey::X));
|
||||
}
|
||||
_copy = items.emplace_back(_createMenuItem(Symbol::Copy, RS_(L"Copy"), { this, &TextMenuFlyout::Copy_Click }, VirtualKeyModifiers::Control, VirtualKey::C));
|
||||
if (writable)
|
||||
{
|
||||
items.emplace_back(_createMenuItem(Symbol::Paste, RS_(L"Paste"), { this, &TextMenuFlyout::Paste_Click }, VirtualKeyModifiers::Control, VirtualKey::V));
|
||||
}
|
||||
items.emplace_back(_createMenuItem(NullSymbol, RS_(L"SelectAll"), { this, &TextMenuFlyout::SelectAll_Click }, VirtualKeyModifiers::Control, VirtualKey::A));
|
||||
|
||||
Items().ReplaceAll({ items.data(), gsl::narrow_cast<uint32_t>(items.size()) });
|
||||
}
|
||||
|
||||
const auto visibilityOfItemsRequiringSelection = selection.empty() ? Visibility::Collapsed : Visibility::Visible;
|
||||
if (_cut)
|
||||
{
|
||||
_cut.Visibility(visibilityOfItemsRequiringSelection);
|
||||
}
|
||||
_copy.Visibility(visibilityOfItemsRequiringSelection);
|
||||
}
|
||||
|
||||
void TextMenuFlyout::Cut_Click(IInspectable const&, RoutedEventArgs const&)
|
||||
{
|
||||
// NOTE: When the flyout closes, WinUI doesn't disconnect the accelerator keys.
|
||||
// Since that means we'll get Ctrl+X/C/V callbacks forever, just ignore them.
|
||||
// The TextBox will still handle those events...
|
||||
auto target = Target();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto box = target.try_as<NumberBox>())
|
||||
{
|
||||
target = box.GetTemplateChild(L"InputBox").as<TextBox>();
|
||||
}
|
||||
if (const auto control = target.try_as<TextBox>())
|
||||
{
|
||||
control.CutSelectionToClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
void TextMenuFlyout::Copy_Click(IInspectable const&, RoutedEventArgs const&)
|
||||
{
|
||||
auto target = Target();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto box = target.try_as<NumberBox>())
|
||||
{
|
||||
target = box.GetTemplateChild(L"InputBox").as<TextBox>();
|
||||
}
|
||||
if (const auto control = target.try_as<TextBlock>())
|
||||
{
|
||||
control.CopySelectionToClipboard();
|
||||
}
|
||||
else if (const auto control = target.try_as<TextBox>())
|
||||
{
|
||||
control.CopySelectionToClipboard();
|
||||
}
|
||||
else if (const auto control = target.try_as<RichTextBlock>())
|
||||
{
|
||||
control.CopySelectionToClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
void TextMenuFlyout::Paste_Click(IInspectable const&, RoutedEventArgs const&)
|
||||
{
|
||||
auto target = Target();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto box = target.try_as<NumberBox>())
|
||||
{
|
||||
target = box.GetTemplateChild(L"InputBox").as<TextBox>();
|
||||
}
|
||||
if (const auto control = target.try_as<TextBox>())
|
||||
{
|
||||
control.PasteFromClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
void TextMenuFlyout::SelectAll_Click(IInspectable const&, RoutedEventArgs const&)
|
||||
{
|
||||
// BODGY:
|
||||
// Once the flyout was open once, we'll get Ctrl+A events and the TextBox will
|
||||
// ignore them. As such, we have to dig out the focused element as a fallback,
|
||||
// because otherwise Ctrl+A will be permanently broken. Put differently,
|
||||
// this is bodgy because WinUI 2.8 is buggy. There's no other solution here.
|
||||
IInspectable target = Target();
|
||||
if (!target)
|
||||
{
|
||||
target = FocusManager::GetFocusedElement(XamlRoot());
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto box = target.try_as<NumberBox>())
|
||||
{
|
||||
target = box.GetTemplateChild(L"InputBox").as<TextBox>();
|
||||
}
|
||||
if (const auto control = target.try_as<TextBlock>())
|
||||
{
|
||||
control.SelectAll();
|
||||
}
|
||||
else if (const auto control = target.try_as<TextBox>())
|
||||
{
|
||||
control.SelectAll();
|
||||
}
|
||||
else if (const auto control = target.try_as<RichTextBlock>())
|
||||
{
|
||||
control.SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
MenuFlyoutItemBase TextMenuFlyout::_createMenuItem(Symbol symbol, hstring text, RoutedEventHandler click, VirtualKeyModifiers modifiers, VirtualKey key)
|
||||
{
|
||||
KeyboardAccelerator accel;
|
||||
accel.Modifiers(modifiers);
|
||||
accel.Key(key);
|
||||
|
||||
MenuFlyoutItem item;
|
||||
if (symbol != NullSymbol)
|
||||
{
|
||||
item.Icon(SymbolIcon{ std::move(symbol) });
|
||||
}
|
||||
item.Text(std::move(text));
|
||||
item.Click(std::move(click));
|
||||
item.KeyboardAccelerators().Append(std::move(accel));
|
||||
return item;
|
||||
}
|
||||
}
|
||||
43
src/cascadia/UIHelpers/TextMenuFlyout.h
Normal file
43
src/cascadia/UIHelpers/TextMenuFlyout.h
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TextMenuFlyout.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
{
|
||||
// This custom flyout exists because WinUI 2 only supports 1 text block flyout
|
||||
// *per thread* not per window. If you have >1 window per 1 thread, as we do,
|
||||
// the focus will just be delegated to the window the flyout was first opened in.
|
||||
// Once the first window is gone, WinUI will either do nothing or crash.
|
||||
// See: GH#18599
|
||||
struct TextMenuFlyout : TextMenuFlyoutT<TextMenuFlyout>
|
||||
{
|
||||
TextMenuFlyout();
|
||||
|
||||
void MenuFlyout_Opening(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& e);
|
||||
void Cut_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void Copy_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void Paste_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void SelectAll_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
|
||||
private:
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase _createMenuItem(
|
||||
winrt::Windows::UI::Xaml::Controls::Symbol symbol,
|
||||
winrt::hstring text,
|
||||
winrt::Windows::UI::Xaml::RoutedEventHandler click,
|
||||
winrt::Windows::System::VirtualKeyModifiers modifiers,
|
||||
winrt::Windows::System::VirtualKey key);
|
||||
|
||||
// These are always present.
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase _copy{ nullptr };
|
||||
// These are only set for writable controls.
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItemBase _cut{ nullptr };
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::UI::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(TextMenuFlyout);
|
||||
}
|
||||
7
src/cascadia/UIHelpers/TextMenuFlyout.idl
Normal file
7
src/cascadia/UIHelpers/TextMenuFlyout.idl
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Microsoft.Terminal.UI
|
||||
{
|
||||
[default_interface] runtimeclass TextMenuFlyout : Windows.UI.Xaml.Controls.MenuFlyout
|
||||
{
|
||||
TextMenuFlyout();
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,9 @@
|
||||
<ClInclude Include="ResourceString.h">
|
||||
<DependentUpon>ResourceString.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TextMenuFlyout.h">
|
||||
<DependentUpon>TextMenuFlyout.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="init.cpp" />
|
||||
@ -42,6 +45,9 @@
|
||||
<ClCompile Include="ResourceString.cpp">
|
||||
<DependentUpon>ResourceString.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TextMenuFlyout.cpp">
|
||||
<DependentUpon>TextMenuFlyout.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -49,6 +55,13 @@
|
||||
<Midl Include="IconPathConverter.idl" />
|
||||
<Midl Include="IDirectKeyListener.idl" />
|
||||
<Midl Include="ResourceString.idl" />
|
||||
<Midl Include="TextMenuFlyout.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Resources\en-US\Resources.resw">
|
||||
<SubType>Designer</SubType>
|
||||
</PRIResource>
|
||||
<OCResourceDirectory Include="Resources" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
@ -65,15 +78,12 @@
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);user32.lib;shell32.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
|
||||
</Project>
|
||||
|
||||
@ -31,8 +31,18 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="ResourceString.idl" />
|
||||
<Midl Include="Converters.idl" />
|
||||
<Midl Include="IconPathConverter.idl" />
|
||||
<Midl Include="IDirectKeyListener.idl" />
|
||||
<Midl Include="TextMenuFlyout.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="Resources\en-US\Resources.resw">
|
||||
<Filter>Resources\en-US</Filter>
|
||||
</PRIResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -3,6 +3,8 @@
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#pragma warning(suppress : 26440) // Not interested in changing the specification of DllMain to make it noexcept given it's an interface to the OS.
|
||||
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
|
||||
{
|
||||
@ -11,11 +13,11 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID /*reserved*/)
|
||||
case DLL_PROCESS_ATTACH:
|
||||
DisableThreadLibraryCalls(hInstDll);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"Microsoft.Terminal.UI/Resources")
|
||||
|
||||
@ -25,16 +25,15 @@
|
||||
|
||||
#include <wil/cppwinrt.h>
|
||||
|
||||
#include <winrt/Windows.ApplicationModel.Resources.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
|
||||
#include <winrt/Windows.Graphics.Imaging.h>
|
||||
#include <Windows.Graphics.Imaging.Interop.h>
|
||||
|
||||
#include <winrt/Windows.UI.Text.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.h>
|
||||
#include <winrt/Windows.UI.Xaml.Markup.h>
|
||||
#include <winrt/Windows.UI.Xaml.Media.h>
|
||||
#include <winrt/Windows.UI.Xaml.Input.h>
|
||||
#include <winrt/Windows.UI.Xaml.Media.Imaging.h>
|
||||
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.Terminal.UI.Markdown"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
@ -201,7 +202,11 @@
|
||||
VerticalAlignment="Stretch">
|
||||
<TextBlock FontFamily="Cascadia Mono, Consolas"
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind Commandlines}" />
|
||||
Text="{x:Bind Commandlines}">
|
||||
<TextBlock.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBlock.ContextFlyout>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
@ -72,6 +72,7 @@ WUX::Controls::RichTextBlock MarkdownToXaml::Convert(std::string_view markdownTe
|
||||
MarkdownToXaml::MarkdownToXaml(const winrt::hstring& baseUrl) :
|
||||
_baseUri{ baseUrl }
|
||||
{
|
||||
_root.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{});
|
||||
_root.IsTextSelectionEnabled(true);
|
||||
_root.TextWrapping(WUX::TextWrapping::WrapWholeWords);
|
||||
}
|
||||
@ -155,6 +156,7 @@ void MarkdownToXaml::_EndParagraph() noexcept
|
||||
WUX::Controls::TextBlock MarkdownToXaml::_makeDefaultTextBlock()
|
||||
{
|
||||
WUX::Controls::TextBlock b{};
|
||||
b.ContextFlyout(winrt::Microsoft::Terminal::UI::TextMenuFlyout{});
|
||||
b.IsTextSelectionEnabled(true);
|
||||
b.TextWrapping(WUX::TextWrapping::WrapWholeWords);
|
||||
return b;
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<OpenConsoleUniversalApp>true</OpenConsoleUniversalApp>
|
||||
|
||||
<!-- C++/WinRT sets the depth to 1 if there is a XAML file in the project
|
||||
Unfortunately for us, we need it to be 4. When the namespace merging
|
||||
depth is 1, Microsoft.Terminal.UI.Markdown becomes "Microsoft",
|
||||
@ -78,10 +77,11 @@
|
||||
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\UIHelpers\UIHelpers.vcxproj">
|
||||
<Project>{6515f03f-e56d-4db4-b23d-ac4fb80db36f}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
|
||||
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
|
||||
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
|
||||
</Project>
|
||||
|
||||
@ -25,14 +25,20 @@
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Module</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MarkdownToXaml.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="MarkdownToXaml.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="Builder.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<ItemGroup>
|
||||
<Page Include="CodeBlock.xaml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -25,21 +25,19 @@
|
||||
|
||||
#include <wil/cppwinrt.h>
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
|
||||
#include <winrt/Windows.UI.Text.h>
|
||||
#include <winrt/Windows.UI.Xaml.Controls.h>
|
||||
#include <winrt/Windows.UI.Xaml.Markup.h>
|
||||
#include <winrt/Windows.UI.Xaml.Media.h>
|
||||
#include <winrt/Windows.UI.Xaml.Media.Imaging.h>
|
||||
#include <winrt/Windows.UI.Xaml.Documents.h>
|
||||
#include <winrt/Windows.UI.Xaml.Input.h>
|
||||
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
|
||||
|
||||
#include <winrt/Microsoft.Terminal.UI.h>
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user