mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 18:43:54 -06:00
Add support for multiple panes in the same window (#825)
* Start working on adding support for panes See #1000 for the panes megathread on remaining work. The functionality will be there, but the keybinding won't be there, so people have to opt-in to it.
This commit is contained in:
parent
31b614d5b2
commit
2da5b0b146
232
doc/cascadia/Panes.md
Normal file
232
doc/cascadia/Panes.md
Normal file
@ -0,0 +1,232 @@
|
||||
---
|
||||
author: "Mike Griese @zadjii-msft"
|
||||
created on: 2019-May-16
|
||||
---
|
||||
|
||||
# Panes in the Windows Terminal
|
||||
|
||||
## Abstract
|
||||
|
||||
Panes are an abstraction by which the terminal can display multiple terminal
|
||||
instances simultaneously in a single terminal window. While tabs allow for a
|
||||
single terminal window to have many terminal sessions running simultaneously
|
||||
within a single window, only one tab can be visible at a time. Panes, on the
|
||||
other hand, allow a user to have many different terminal sessions visible to the
|
||||
user within the context of a single window at the same time. This can enable
|
||||
greater productivity from the user, as they can see the output of one terminal
|
||||
window while working in another.
|
||||
|
||||
This spec will help outline the design of the implementation of panes in the
|
||||
Windows Terminal.
|
||||
|
||||
## Inspirations
|
||||
|
||||
Panes within the context of a single terminal window are not a new idea. The
|
||||
design of the panes for the Windows Terminal was heavily inspired by the
|
||||
application `tmux`, which is a commandline application which acts as a "terminal
|
||||
multiplexer", allowing for the easy managment of many terminal sessions from a
|
||||
single application.
|
||||
|
||||
Other applications that include pane-like functionality include (but are not
|
||||
limited to):
|
||||
|
||||
* screen
|
||||
* terminator
|
||||
* emacs & vim
|
||||
* Iterm2
|
||||
|
||||
## Design
|
||||
|
||||
The architecture of the Windows Terminal can be broken into two main pieces:
|
||||
Tabs and Panes. The Windows Terminal supports _top-level_ tabs, with nested
|
||||
panes inside the tabs. This means that there's a single strip of tabs along the
|
||||
application, and each tab has a set of panes that are visible within the context
|
||||
of that tab.
|
||||
|
||||
Panes are implemented as a binary tree of panes. A Pane can either be a leaf
|
||||
pane, with it's own terminal control that it displays, or it could be a parent
|
||||
pane, where it has two children, each with their own terminal control.
|
||||
|
||||
When a pane is a parent, its two children are either split vertically or
|
||||
horizontally. Parent nodes don't have a terminal of their own, they merely
|
||||
display the terminals of their children.
|
||||
|
||||
* If a Pane is split vertically, the two panes are seperated by a vertical
|
||||
split, as to appear side-by-side. Think `[|]`
|
||||
* If a Pane is split horizontally, the two panes are split by a horizontal
|
||||
separator, and appear above/below one another. Think `[-]`.
|
||||
|
||||
As additional panes are created, panes will continue to subdivide the space of
|
||||
their parent. It's up to the parent pane to control the sizing and display of
|
||||
it's children.
|
||||
|
||||
### Example
|
||||
|
||||
We'll start by taking the terminal and creating a single vertical split. There
|
||||
are now two panes in the terminal, side by side. The original terminal is `A`,
|
||||
and the newly created one is `B`. The terminal now looks like this:
|
||||
|
||||
```
|
||||
+---------------+
|
||||
| | | 1: parent [|]
|
||||
| | | ├── 2: A
|
||||
| | | └── 3: B
|
||||
| A | B |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
Here, there are actually 3 nodes: 1 is the parent of both 2 and 3. 2 is the node
|
||||
containing the `A` terminal, and 3 is the node with the `B` terminal.
|
||||
|
||||
|
||||
We could now split `B` in two horizontally, creating a third terminal pane `C`.
|
||||
|
||||
```
|
||||
+---------------+
|
||||
| | | 1: parent [|]
|
||||
| | B | ├── 2: A
|
||||
| | | └── 3: parent [-]
|
||||
| A +-------+ ├── 4: B
|
||||
| | | └── 5: C
|
||||
| | C |
|
||||
| | |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
Node 3 is now a parent node, and the terminal `B` has moved into a new node as a
|
||||
sibling of the new terminal `C`.
|
||||
|
||||
We could also split `A` in horizontally, creating a fourth terminal pane `D`.
|
||||
|
||||
```
|
||||
+---------------+
|
||||
| | | 1: parent [|]
|
||||
| A | B | ├── 2: parent [-]
|
||||
| | | | ├── 4: A
|
||||
+-------+-------+ | └── 5: D
|
||||
| | | └── 3: parent [-]
|
||||
| D | C | ├── 4: B
|
||||
| | | └── 5: C
|
||||
+---------------+
|
||||
```
|
||||
|
||||
While it may appear that there's a single horizonal separator and a single
|
||||
vertical separator here, that's not actually the case. Due to the tree-like
|
||||
structure of the pane splitting, the horizontal splits exist only between the
|
||||
two panes they're splitting. So, the user could move each of the horizontal
|
||||
splits independently, without affecting the other set of panes. As an example:
|
||||
|
||||
```
|
||||
+---------------+
|
||||
| | |
|
||||
| A | |
|
||||
+-------+ B |
|
||||
| | |
|
||||
| D | |
|
||||
| +-------+
|
||||
| | C |
|
||||
+---------------+
|
||||
```
|
||||
|
||||
### Creating a pane
|
||||
|
||||
In the basic use case, the user will decide to split the currently focused pane.
|
||||
The currently focused pane is always a leaf, because as parent's can't be
|
||||
focused (they don't have their own terminal). When a user decides to add a new
|
||||
pane, the child will:
|
||||
|
||||
1. Convert into a parent
|
||||
2. Move its terminal into its first child
|
||||
3. Split its UI in half, and display each child in one half.
|
||||
|
||||
It's up to the app hosting the panes to tell the pane what kind of terminal in
|
||||
wants created in the new pane. By default, the new pane will be created with the
|
||||
default settings profile.
|
||||
|
||||
### While panes are open
|
||||
|
||||
When a tab has multiple panes open, only one is the "active" pane. This is the
|
||||
pane that was last focused in the tab. If the tab is the currently open tab,
|
||||
then this is the pane with the currently focused terminal control. When the user
|
||||
brings the tab into focus, the last focused pane is the pane that should become
|
||||
focused again.
|
||||
|
||||
The tab's state will be updated to reflect the state of it's focused pane. The
|
||||
title text and icon of the tab will reflect that of the focused pane. Should the
|
||||
focus switch from one pane to another, the tab's text and icon should update to
|
||||
reflect the newly focused control. Any additional state that the tab would
|
||||
display for a single pane should also be reflected in the tab for a tab with
|
||||
multiple panes.
|
||||
|
||||
While panes are open, the user should be able to move any split between panes.
|
||||
In moving the split, the sizes of the terminal controls should be resized to
|
||||
match.
|
||||
|
||||
### Closing a pane
|
||||
|
||||
A pane can either be closed by the user manually, or when the terminal it's
|
||||
attached to raises its ConnectionClosed event. When this happens, we should
|
||||
remove this pane from the tree. The parent of the closing pane will have to
|
||||
remove the pane as one of it's children. If the sibling of the closing pane is a
|
||||
leaf, then the parent should just take all of the state from the remaining pane.
|
||||
This will cause the remaining pane's content to expand to take the entire
|
||||
boundaries of the parent's pane. If the remaining child was a parent itself,
|
||||
then the parent will take both the children of the remaining pane, and make them
|
||||
the parent's children, as if the parent node was taken from the tree and
|
||||
replaced by the remaining child.
|
||||
|
||||
## Future considerations
|
||||
|
||||
The Pane implementation isn't complete in it's current form. There are many
|
||||
additional things that could be done to improve the user experience. This is by
|
||||
no means a comprehensive list.
|
||||
|
||||
* [ ] Panes should be resizable with the mouse. The user should be able to drag
|
||||
the separator for a pair of panes, and have the content between them resize as
|
||||
the separator moves.
|
||||
* [ ] There's no keyboard shortcut for "ClosePane"
|
||||
* [ ] The user should be able to configure what profile is used for splitting a
|
||||
pane. Currently, the default profile is used, but it's possible a user might
|
||||
want to create a new pane with the parent pane's profile.
|
||||
* [ ] There should be some sort of UI to indicate that a particular pane is
|
||||
focused, more than just the blinking cursor. `tmux` accomplishes this by
|
||||
colorizing the separators adjacent to the active pane. Another idea is
|
||||
displaying a small outline around the focused pane (like when tabbing through
|
||||
controls on a webpage).
|
||||
* [ ] The user should be able to navigate the focus of panes with the keyboard,
|
||||
instead of requiring the mouse.
|
||||
* [ ] The user should be able to zoom a pane, to make the pane take the entire
|
||||
size of the terminal window temporarily.
|
||||
* [ ] A pane doesn't necessarily need to host a terminal. It could potentially
|
||||
host another UIElement. One could imagine enabling a user to quickly open up a
|
||||
Browser pane to search for a particular string without needing to leave the
|
||||
terminal.
|
||||
|
||||
## Footnotes
|
||||
|
||||
### Why not top-level panes, and nested tabs?
|
||||
|
||||
If each pane were to have it's own set of tabs, then each pane would need to
|
||||
reserve screen real estate for a row of tabs. As a user continued to split the
|
||||
window, more and more of the screen would be dedicated to just displaying a row
|
||||
of tabs, which isn't really the important part of the application, the terminal
|
||||
is.
|
||||
|
||||
Additionally, if there were top-level panes, once the root was split, it would
|
||||
not be possible to move a single pane to be the full size of the window. The
|
||||
user would need to somehow close the other panes, to be able to make the split
|
||||
the size of the dull window.
|
||||
|
||||
One con of this design is that if a control is hosted in a pane, the current
|
||||
design makes it hard to move out of a pane into it's own tab, or into another
|
||||
pane. This could be solved a number of ways. There could be keyboard shortcuts
|
||||
for swapping the positions of tabs, or a shortcut for both "zooming" a tab
|
||||
(temporarily making it the full size) or even popping a pane out to it's own
|
||||
tab. Additionally, a right-click menu option could be added to do the
|
||||
aformentioned actions. Discoverability of these two actions is not as high as
|
||||
just dragging a tab from one pane to another; however, it's believed that panes
|
||||
are more of a power-user scenario, and power users will not neccessarily be
|
||||
turned off by the feature's discoverability.
|
||||
@ -424,6 +424,8 @@ namespace winrt::TerminalApp::implementation
|
||||
bindings.ScrollDown([this]() { _Scroll(1); });
|
||||
bindings.NextTab([this]() { _SelectNextTab(true); });
|
||||
bindings.PrevTab([this]() { _SelectNextTab(false); });
|
||||
bindings.SplitVertical([this]() { _SplitVertical(std::nullopt); });
|
||||
bindings.SplitHorizontal([this]() { _SplitHorizontal(std::nullopt); });
|
||||
bindings.ScrollUpPage([this]() { _ScrollPage(-1); });
|
||||
bindings.ScrollDownPage([this]() { _ScrollPage(1); });
|
||||
bindings.SwitchToTab([this](const auto index) { _SelectTab({ index }); });
|
||||
@ -579,23 +581,16 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
for (auto &tab : _tabs)
|
||||
{
|
||||
const auto term = tab->GetTerminalControl();
|
||||
const GUID tabProfile = tab->GetProfile();
|
||||
|
||||
if (profileGuid == tabProfile)
|
||||
{
|
||||
term.UpdateSettings(settings);
|
||||
|
||||
// Update the icons of the tabs with this profile open.
|
||||
auto tabViewItem = tab->GetTabViewItem();
|
||||
tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [profile, tabViewItem]() {
|
||||
// _GetIconFromProfile has to run on the main thread
|
||||
tabViewItem.Icon(App::_GetIconFromProfile(profile));
|
||||
});
|
||||
}
|
||||
// Attempt to reload the settings of any panes with this profile
|
||||
tab->UpdateSettings(settings, profileGuid);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the icon of the tab for the currently focused profile in that tab.
|
||||
for (auto& tab : _tabs)
|
||||
{
|
||||
_UpdateTabIcon(tab);
|
||||
}
|
||||
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
|
||||
// Refresh the UI theme
|
||||
@ -608,6 +603,50 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the icon of the currently focused terminal control, and set its
|
||||
// tab's icon to that icon.
|
||||
// Arguments:
|
||||
// - tab: the Tab to update the title for.
|
||||
void App::_UpdateTabIcon(std::shared_ptr<Tab> tab)
|
||||
{
|
||||
const auto lastFocusedProfileOpt = tab->GetFocusedProfile();
|
||||
if (lastFocusedProfileOpt.has_value())
|
||||
{
|
||||
const auto lastFocusedProfile = lastFocusedProfileOpt.value();
|
||||
|
||||
auto tabViewItem = tab->GetTabViewItem();
|
||||
tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, lastFocusedProfile, tabViewItem]() {
|
||||
// _GetIconFromProfile has to run on the main thread
|
||||
const auto* const matchingProfile = _settings->FindProfile(lastFocusedProfile);
|
||||
if (matchingProfile)
|
||||
{
|
||||
tabViewItem.Icon(App::_GetIconFromProfile(*matchingProfile));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the title of the currently focused terminal control, and set it's
|
||||
// tab's text to that text. If this tab is the focused tab, then also
|
||||
// bubble this title to any listeners of our TitleChanged event.
|
||||
// Arguments:
|
||||
// - tab: the Tab to update the title for.
|
||||
void App::_UpdateTitle(std::shared_ptr<Tab> tab)
|
||||
{
|
||||
auto newTabTitle = tab->GetFocusedTitle();
|
||||
|
||||
// TODO #608: If the settings don't want the terminal's text in the
|
||||
// tab, then display something else.
|
||||
tab->SetTabText(newTabTitle);
|
||||
if (_settings->GlobalSettings().GetShowTitleInTitlebar() &&
|
||||
tab->IsFocused())
|
||||
{
|
||||
_titleChangeHandlers(newTabTitle);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update the current theme of the application. This will manually update
|
||||
// all of the elements in our UI to match the given theme.
|
||||
@ -727,6 +766,60 @@ namespace winrt::TerminalApp::implementation
|
||||
eventArgs.HandleClipboardData(text);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Connects event handlers to the TermControl for events that we want to
|
||||
// handle. This includes:
|
||||
// * the Copy and Paste events, for setting and retrieving clipboard data
|
||||
// on the right thread
|
||||
// * the TitleChanged event, for changing the text of the tab
|
||||
// * the GotFocus event, for changing the title/icon in the tab when a new
|
||||
// control is focused
|
||||
// Arguments:
|
||||
// - term: The newly created TermControl to connect the events for
|
||||
// - hostingTab: The Tab that's hosting this TermControl instance
|
||||
void App::_RegisterTerminalEvents(TermControl term, std::shared_ptr<Tab> hostingTab)
|
||||
{
|
||||
// Add an event handler when the terminal's selection wants to be copied.
|
||||
// When the text buffer data is retrieved, we'll copy the data into the Clipboard
|
||||
term.CopyToClipboard({ this, &App::_CopyToClipboardHandler });
|
||||
|
||||
// Add an event handler when the terminal wants to paste data from the Clipboard.
|
||||
term.PasteFromClipboard({ this, &App::_PasteFromClipboardHandler });
|
||||
|
||||
// Don't capture a strong ref to the tab. If the tab is removed as this
|
||||
// is called, we don't really care anymore about handling the event.
|
||||
std::weak_ptr<Tab> weakTabPtr = hostingTab;
|
||||
term.TitleChanged([this, weakTabPtr](auto newTitle){
|
||||
auto tab = weakTabPtr.lock();
|
||||
if (!tab)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// The title of the control changed, but not necessarily the title
|
||||
// of the tab. Get the title of the focused pane of the tab, and set
|
||||
// the tab's text to the focused panes' text.
|
||||
_UpdateTitle(tab);
|
||||
});
|
||||
|
||||
term.GetControl().GotFocus([this, weakTabPtr](auto&&, auto&&)
|
||||
{
|
||||
auto tab = weakTabPtr.lock();
|
||||
if (!tab)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Update the focus of the tab's panes
|
||||
tab->UpdateFocus();
|
||||
|
||||
// Possibly update the title of the tab, window to match the newly
|
||||
// focused pane.
|
||||
_UpdateTitle(tab);
|
||||
|
||||
// Possibly update the icon of the tab.
|
||||
_UpdateTabIcon(tab);
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates a new tab with the given settings. If the tab bar is not being
|
||||
// currently displayed, it will be shown.
|
||||
@ -737,41 +830,11 @@ namespace winrt::TerminalApp::implementation
|
||||
// Initialize the new tab
|
||||
TermControl term{ settings };
|
||||
|
||||
// Add an event handler when the terminal's selection wants to be copied.
|
||||
// When the text buffer data is retrieved, we'll copy the data into the Clipboard
|
||||
term.CopyToClipboard([=](auto copiedData) {
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::High, [copiedData]() {
|
||||
DataPackage dataPack = DataPackage();
|
||||
dataPack.RequestedOperation(DataPackageOperation::Copy);
|
||||
dataPack.SetText(copiedData);
|
||||
Clipboard::SetContent(dataPack);
|
||||
|
||||
// TODO: MSFT 20642290 and 20642291
|
||||
// rtf copy and html copy
|
||||
});
|
||||
});
|
||||
|
||||
// Add an event handler when the terminal wants to paste data from the Clipboard.
|
||||
term.PasteFromClipboard([=](auto /*sender*/, auto eventArgs) {
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::High, [eventArgs]() {
|
||||
PasteFromClipboard(eventArgs);
|
||||
});
|
||||
});
|
||||
|
||||
// Add the new tab to the list of our tabs.
|
||||
auto newTab = _tabs.emplace_back(std::make_shared<Tab>(profileGuid, term));
|
||||
|
||||
// Add an event handler when the terminal's title changes. When the
|
||||
// title changes, we'll bubble it up to listeners of our own title
|
||||
// changed event, so they can handle it.
|
||||
newTab->GetTerminalControl().TitleChanged([=](auto newTitle){
|
||||
// Only bubble the change if this tab is the focused tab.
|
||||
if (_settings->GlobalSettings().GetShowTitleInTitlebar() &&
|
||||
newTab->IsFocused())
|
||||
{
|
||||
_titleChangeHandlers(newTitle);
|
||||
}
|
||||
});
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(term, newTab);
|
||||
|
||||
auto tabViewItem = newTab->GetTabViewItem();
|
||||
_tabView.Items().Append(tabViewItem);
|
||||
@ -784,24 +847,15 @@ namespace winrt::TerminalApp::implementation
|
||||
tabViewItem.Icon(_GetIconFromProfile(*profile));
|
||||
}
|
||||
|
||||
// Add an event handler when the terminal's connection is closed.
|
||||
newTab->GetTerminalControl().ConnectionClosed([=]() {
|
||||
_tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [newTab, tabViewItem, this]() {
|
||||
const GUID tabProfile = newTab->GetProfile();
|
||||
// Don't just capture this pointer, because the profile might
|
||||
// get destroyed before this is called (case in point -
|
||||
// reloading settings)
|
||||
const auto* const p = _settings->FindProfile(tabProfile);
|
||||
tabViewItem.PointerPressed({ this, &App::_OnTabClick });
|
||||
|
||||
if (p != nullptr && p->GetCloseOnExit())
|
||||
{
|
||||
_RemoveTabViewItem(tabViewItem);
|
||||
}
|
||||
// When the tab is closed, remove it from our list of tabs.
|
||||
newTab->Closed([tabViewItem, this](){
|
||||
_tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tabViewItem, this]() {
|
||||
_RemoveTabViewItem(tabViewItem);
|
||||
});
|
||||
});
|
||||
|
||||
tabViewItem.PointerPressed({ this, &App::_OnTabClick });
|
||||
|
||||
// This is one way to set the tab's selected background color.
|
||||
// tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), a Brush?);
|
||||
|
||||
@ -865,7 +919,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
delta = std::clamp(delta, -1, 1);
|
||||
const auto focusedTabIndex = _GetFocusedTabIndex();
|
||||
const auto control = _tabs[focusedTabIndex]->GetTerminalControl();
|
||||
const auto control = _GetFocusedControl();
|
||||
const auto termHeight = control.GetViewHeight();
|
||||
_tabs[focusedTabIndex]->Scroll(termHeight * delta);
|
||||
}
|
||||
@ -877,10 +931,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// and get text to appear on separate lines.
|
||||
void App::_CopyText(const bool trimTrailingWhitespace)
|
||||
{
|
||||
const int focusedTabIndex = _GetFocusedTabIndex();
|
||||
std::shared_ptr<Tab> focusedTab{ _tabs[focusedTabIndex] };
|
||||
|
||||
const auto control = focusedTab->GetTerminalControl();
|
||||
const auto control = _GetFocusedControl();
|
||||
control.CopySelectionToClipboard(trimTrailingWhitespace);
|
||||
}
|
||||
|
||||
@ -930,10 +981,9 @@ namespace winrt::TerminalApp::implementation
|
||||
try
|
||||
{
|
||||
auto tab = _tabs.at(selectedIndex);
|
||||
auto control = tab->GetTerminalControl().GetControl();
|
||||
|
||||
_tabContent.Children().Clear();
|
||||
_tabContent.Children().Append(control);
|
||||
_tabContent.Children().Append(tab->GetRootElement());
|
||||
|
||||
tab->SetFocused(true);
|
||||
_titleChangeHandlers(GetTitle());
|
||||
@ -986,8 +1036,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
try
|
||||
{
|
||||
auto tab = _tabs.at(selectedIndex);
|
||||
return tab->GetTerminalControl().Title();
|
||||
return _GetFocusedControl().Title();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
@ -1076,6 +1125,98 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl App::_GetFocusedControl()
|
||||
{
|
||||
int focusedTabIndex = _GetFocusedTabIndex();
|
||||
auto focusedTab = _tabs[focusedTabIndex];
|
||||
return focusedTab->GetFocusedTerminalControl();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Vertically split the focused pane, and place the given TermControl into
|
||||
// the newly created pane.
|
||||
// Arguments:
|
||||
// - profile: The profile GUID to associate with the newly created pane. If
|
||||
// this is nullopt, use the default profile.
|
||||
void App::_SplitVertical(const std::optional<GUID>& profileGuid)
|
||||
{
|
||||
_SplitPane(Pane::SplitState::Vertical, profileGuid);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Horizontally split the focused pane and place the given TermControl
|
||||
// into the newly created pane.
|
||||
// Arguments:
|
||||
// - profile: The profile GUID to associate with the newly created pane. If
|
||||
// this is nullopt, use the default profile.
|
||||
void App::_SplitHorizontal(const std::optional<GUID>& profileGuid)
|
||||
{
|
||||
_SplitPane(Pane::SplitState::Horizontal, profileGuid);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Split the focused pane either horizontally or vertically, and place the
|
||||
// given TermControl into the newly created pane.
|
||||
// - If splitType == SplitState::None, this method does nothing.
|
||||
// Arguments:
|
||||
// - splitType: one value from the Pane::SplitState enum, indicating how the
|
||||
// new pane should be split from its parent.
|
||||
// - profile: The profile GUID to associate with the newly created pane. If
|
||||
// this is nullopt, use the default profile.
|
||||
void App::_SplitPane(const Pane::SplitState splitType, const std::optional<GUID>& profileGuid)
|
||||
{
|
||||
// Do nothing if we're requesting no split.
|
||||
if (splitType == Pane::SplitState::None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto realGuid = profileGuid ? profileGuid.value() :
|
||||
_settings->GlobalSettings().GetDefaultProfile();
|
||||
const auto controlSettings = _settings->MakeSettings(realGuid);
|
||||
TermControl newControl{ controlSettings };
|
||||
|
||||
const int focusedTabIndex = _GetFocusedTabIndex();
|
||||
auto focusedTab = _tabs[focusedTabIndex];
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(newControl, focusedTab);
|
||||
|
||||
return splitType == Pane::SplitState::Horizontal ? focusedTab->AddHorizontalSplit(realGuid, newControl) :
|
||||
focusedTab->AddVerticalSplit(realGuid, newControl);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Place `copiedData` into the clipboard as text. Triggered when a
|
||||
// terminal control raises it's CopyToClipboard event.
|
||||
// Arguments:
|
||||
// - copiedData: the new string content to place on the clipboard.
|
||||
void App::_CopyToClipboardHandler(const winrt::hstring& copiedData)
|
||||
{
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::High, [copiedData]() {
|
||||
DataPackage dataPack = DataPackage();
|
||||
dataPack.RequestedOperation(DataPackageOperation::Copy);
|
||||
dataPack.SetText(copiedData);
|
||||
Clipboard::SetContent(dataPack);
|
||||
|
||||
// TODO: MSFT 20642290 and 20642291
|
||||
// rtf copy and html copy
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Fires an async event to get data from the clipboard, and paste it to
|
||||
// the terminal. Triggered when the Terminal Control requests clipboard
|
||||
// data with it's PasteFromClipboard event.
|
||||
// Arguments:
|
||||
// - eventArgs: the PasteFromClipboard event sent from the TermControl
|
||||
void App::_PasteFromClipboardHandler(const IInspectable& /*sender*/,
|
||||
const PasteFromClipboardEventArgs& eventArgs)
|
||||
{
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::High, [eventArgs]() {
|
||||
PasteFromClipboard(eventArgs);
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Takes a MenuFlyoutItem and a corresponding KeyChord value and creates the accelerator for UI display.
|
||||
|
||||
@ -89,6 +89,11 @@ namespace winrt::TerminalApp::implementation
|
||||
void _FeedbackButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
void _UpdateTabView();
|
||||
void _UpdateTabIcon(std::shared_ptr<Tab> tab);
|
||||
void _UpdateTitle(std::shared_ptr<Tab> tab);
|
||||
|
||||
|
||||
void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, std::shared_ptr<Tab> hostingTab);
|
||||
|
||||
void _CreateNewTabFromSettings(GUID profileGuid, winrt::Microsoft::Terminal::Settings::TerminalSettings settings);
|
||||
|
||||
@ -102,6 +107,9 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _Scroll(int delta);
|
||||
void _CopyText(const bool trimTrailingWhitespace);
|
||||
void _SplitVertical(const std::optional<GUID>& profileGuid);
|
||||
void _SplitHorizontal(const std::optional<GUID>& profileGuid);
|
||||
void _SplitPane(const Pane::SplitState splitType, const std::optional<GUID>& profileGuid);
|
||||
// Todo: add more event implementations here
|
||||
// MSFT:20641986: Add keybindings for New Window
|
||||
void _ScrollPage(int delta);
|
||||
@ -117,6 +125,12 @@ namespace winrt::TerminalApp::implementation
|
||||
void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme);
|
||||
|
||||
static Windows::UI::Xaml::Controls::IconElement _GetIconFromProfile(const ::TerminalApp::Profile& profile);
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl _GetFocusedControl();
|
||||
|
||||
void _CopyToClipboardHandler(const winrt::hstring& copiedData);
|
||||
void _PasteFromClipboardHandler(const IInspectable& sender, const Microsoft::Terminal::TerminalControl::PasteFromClipboardEventArgs& eventArgs);
|
||||
|
||||
static void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::Settings::KeyChord& keyChord);
|
||||
};
|
||||
}
|
||||
|
||||
@ -114,6 +114,13 @@ namespace winrt::TerminalApp::implementation
|
||||
_PrevTabHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::SplitVertical:
|
||||
_SplitVerticalHandlers();
|
||||
return true;
|
||||
case ShortcutAction::SplitHorizontal:
|
||||
_SplitHorizontalHandlers();
|
||||
return true;
|
||||
|
||||
case ShortcutAction::SwitchToTab0:
|
||||
_SwitchToTabHandlers(0);
|
||||
return true;
|
||||
@ -211,6 +218,8 @@ namespace winrt::TerminalApp::implementation
|
||||
DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, SplitVertical, _SplitVerticalHandlers, TerminalApp::SplitVerticalEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, SplitHorizontal, _SplitHorizontalHandlers, TerminalApp::SplitHorizontalEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs);
|
||||
DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs);
|
||||
|
||||
@ -49,6 +49,8 @@ namespace winrt::TerminalApp::implementation
|
||||
DECLARE_EVENT(SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs);
|
||||
DECLARE_EVENT(NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs);
|
||||
DECLARE_EVENT(PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs);
|
||||
DECLARE_EVENT(SplitVertical, _SplitVerticalHandlers, TerminalApp::SplitVerticalEventArgs);
|
||||
DECLARE_EVENT(SplitHorizontal, _SplitHorizontalHandlers, TerminalApp::SplitHorizontalEventArgs);
|
||||
DECLARE_EVENT(IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs);
|
||||
DECLARE_EVENT(DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs);
|
||||
DECLARE_EVENT(ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs);
|
||||
|
||||
@ -22,6 +22,8 @@ namespace TerminalApp
|
||||
CloseTab,
|
||||
NextTab,
|
||||
PrevTab,
|
||||
SplitVertical,
|
||||
SplitHorizontal,
|
||||
SwitchToTab0,
|
||||
SwitchToTab1,
|
||||
SwitchToTab2,
|
||||
@ -49,6 +51,8 @@ namespace TerminalApp
|
||||
delegate void CloseTabEventArgs();
|
||||
delegate void NextTabEventArgs();
|
||||
delegate void PrevTabEventArgs();
|
||||
delegate void SplitVerticalEventArgs();
|
||||
delegate void SplitHorizontalEventArgs();
|
||||
delegate void SwitchToTabEventArgs(Int32 profileIndex);
|
||||
delegate void IncreaseFontSizeEventArgs();
|
||||
delegate void DecreaseFontSizeEventArgs();
|
||||
@ -76,6 +80,8 @@ namespace TerminalApp
|
||||
event SwitchToTabEventArgs SwitchToTab;
|
||||
event NextTabEventArgs NextTab;
|
||||
event PrevTabEventArgs PrevTab;
|
||||
event SplitVerticalEventArgs SplitVertical;
|
||||
event SplitHorizontalEventArgs SplitHorizontal;
|
||||
event IncreaseFontSizeEventArgs IncreaseFontSize;
|
||||
event DecreaseFontSizeEventArgs DecreaseFontSize;
|
||||
event ScrollUpEventArgs ScrollUp;
|
||||
|
||||
586
src/cascadia/TerminalApp/Pane.cpp
Normal file
586
src/cascadia/TerminalApp/Pane.cpp
Normal file
@ -0,0 +1,586 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Pane.h"
|
||||
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
|
||||
static const int PaneSeparatorSize = 4;
|
||||
|
||||
Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocused) :
|
||||
_control{ control },
|
||||
_lastFocused{ lastFocused },
|
||||
_profile{ profile }
|
||||
{
|
||||
_root.Children().Append(_control.GetControl());
|
||||
_connectionClosedToken = _control.ConnectionClosed({ this, &Pane::_ControlClosedHandler });
|
||||
|
||||
// Set the background of the pane to match that of the theme's default grid
|
||||
// background. This way, we'll match the small underline under the tabs, and
|
||||
// the UI will be consistent on bot light and dark modes.
|
||||
const auto res = Application::Current().Resources();
|
||||
const auto key = winrt::box_value(L"BackgroundGridThemeStyle");
|
||||
if (res.HasKey(key))
|
||||
{
|
||||
const auto g = res.Lookup(key);
|
||||
const auto style = g.try_as<winrt::Windows::UI::Xaml::Style>();
|
||||
// try_as fails by returning nullptr
|
||||
if (style)
|
||||
{
|
||||
_root.Style(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when our attached control is closed. Triggers listeners to our close
|
||||
// event, if we're a leaf pane.
|
||||
// - If this was called, and we became a parent pane (due to work on another
|
||||
// thread), this function will do nothing (allowing the control's new parent
|
||||
// to handle the event instead).
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_ControlClosedHandler()
|
||||
{
|
||||
std::unique_lock lock{ _createCloseLock };
|
||||
// It's possible that this event handler started being executed, then before
|
||||
// we got the lock, another thread created another child. So our control is
|
||||
// actually no longer _our_ control, and instead could be a descendant.
|
||||
//
|
||||
// When the control's new Pane takes ownership of the control, the new
|
||||
// parent will register it's own event handler. That event handler will get
|
||||
// fired after this handler returns, and will properly cleanup state.
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_control.ShouldCloseOnExit())
|
||||
{
|
||||
// Fire our Closed event to tell our parent that we should be removed.
|
||||
_closedHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the root UIElement of this pane. There may be a single TermControl as a
|
||||
// child, or an entire tree of grids and panes as children of this element.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the Grid acting as the root of this pane.
|
||||
Controls::Grid Pane::GetRootElement()
|
||||
{
|
||||
return _root;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - If this is the last focused pane, returns itself. Returns nullptr if this
|
||||
// is a leaf and it's not focused. If it's a parent, it returns nullptr if no
|
||||
// children of this pane were the last pane to be focused, or the Pane that
|
||||
// _was_ the last pane to be focused (if there was one).
|
||||
// - This Pane's control might not currently be focused, if the tab itself is
|
||||
// not currently focused.
|
||||
// Return Value:
|
||||
// - nullptr if we're a leaf and unfocused, or no children were marked
|
||||
// `_lastFocused`, else returns this
|
||||
std::shared_ptr<Pane> Pane::GetFocusedPane()
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
return _lastFocused ? shared_from_this() : nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto firstFocused = _firstChild->GetFocusedPane();
|
||||
if (firstFocused != nullptr)
|
||||
{
|
||||
return firstFocused;
|
||||
}
|
||||
return _secondChild->GetFocusedPane();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns nullptr if no children of this pane were the last control to be
|
||||
// focused, or the TermControl that _was_ the last control to be focused (if
|
||||
// there was one).
|
||||
// - This control might not currently be focused, if the tab itself is not
|
||||
// currently focused.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - nullptr if no children were marked `_lastFocused`, else the TermControl
|
||||
// that was last focused.
|
||||
TermControl Pane::GetFocusedTerminalControl()
|
||||
{
|
||||
auto lastFocused = GetFocusedPane();
|
||||
return lastFocused ? lastFocused->_control : nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns nullopt if no children of this pane were the last control to be
|
||||
// focused, or the GUID of the profile of the last control to be focused (if
|
||||
// there was one).
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - nullopt if no children of this pane were the last control to be
|
||||
// focused, else the GUID of the profile of the last control to be focused
|
||||
std::optional<GUID> Pane::GetFocusedProfile()
|
||||
{
|
||||
auto lastFocused = GetFocusedPane();
|
||||
return lastFocused ? lastFocused->_profile : std::nullopt;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if this pane was the last pane to be focused in a tree of panes.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff we were the last pane focused in this tree of panes.
|
||||
bool Pane::WasLastFocused() const noexcept
|
||||
{
|
||||
return _lastFocused;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true iff this pane has no child panes.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff this pane has no child panes.
|
||||
bool Pane::_IsLeaf() const noexcept
|
||||
{
|
||||
return _splitState == SplitState::None;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if this pane is currently focused, or there is a pane which is
|
||||
// a child of this pane that is actively focused
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if the currently focused pane is either this pane, or one of this
|
||||
// pane's descendants
|
||||
bool Pane::_HasFocusedChild() const noexcept
|
||||
{
|
||||
// We're intentionally making this one giant expression, so the compiler
|
||||
// will skip the following lookups if one of the lookups before it returns
|
||||
// true
|
||||
return (_control && _control.GetControl().FocusState() != FocusState::Unfocused) ||
|
||||
(_firstChild && _firstChild->_HasFocusedChild()) ||
|
||||
(_secondChild && _secondChild->_HasFocusedChild());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update the focus state of this pane, and all its descendants.
|
||||
// * If this is a leaf node, and our control is actively focused, we'll mark
|
||||
// ourselves as the _lastFocused.
|
||||
// * If we're not a leaf, we'll recurse on our children to check them.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::UpdateFocus()
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
const auto controlFocused = _control &&
|
||||
_control.GetControl().FocusState() != FocusState::Unfocused;
|
||||
|
||||
_lastFocused = controlFocused;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastFocused = false;
|
||||
_firstChild->UpdateFocus();
|
||||
_secondChild->UpdateFocus();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Focuses this control if we're a leaf, or attempts to focus the first leaf
|
||||
// of our first child, recursively.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_FocusFirstChild()
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
_control.GetControl().Focus(FocusState::Programmatic);
|
||||
}
|
||||
else
|
||||
{
|
||||
_firstChild->_FocusFirstChild();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to update the settings of this pane or any children of this pane.
|
||||
// * If this pane is a leaf, and our profile guid matches the parameter, then
|
||||
// we'll apply the new settings to our control.
|
||||
// * If we're not a leaf, we'll recurse on our children.
|
||||
// Arguments:
|
||||
// - settings: The new TerminalSettings to apply to any matching controls
|
||||
// - profile: The GUID of the profile these settings should apply to.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::UpdateSettings(const TerminalSettings& settings, const GUID& profile)
|
||||
{
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
_firstChild->UpdateSettings(settings, profile);
|
||||
_secondChild->UpdateSettings(settings, profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (profile == _profile)
|
||||
{
|
||||
_control.UpdateSettings(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Closes one of our children. In doing so, takes the control from the other
|
||||
// child, and makes this pane a leaf node again.
|
||||
// Arguments:
|
||||
// - closeFirst: if true, the first child should be closed, and the second
|
||||
// should be preserved, and vice-versa for false.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_CloseChild(const bool closeFirst)
|
||||
{
|
||||
// Lock the create/close lock so that another operation won't concurrently
|
||||
// modify our tree
|
||||
std::unique_lock lock{ _createCloseLock };
|
||||
|
||||
// If we're a leaf, then chances are both our children closed in close
|
||||
// succession. We waited on the lock while the other child was closed, so
|
||||
// now we don't have a child to close anymore. Return here. When we moved
|
||||
// the non-closed child into us, we also set up event handlers that will be
|
||||
// triggered when we return from this.
|
||||
if (_IsLeaf())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto closedChild = closeFirst ? _firstChild : _secondChild;
|
||||
auto remainingChild = closeFirst ? _secondChild : _firstChild;
|
||||
|
||||
// If the only child left is a leaf, that means we're a leaf now.
|
||||
if (remainingChild->_IsLeaf())
|
||||
{
|
||||
// take the control and profile of the pane that _wasn't_ closed.
|
||||
_control = remainingChild->_control;
|
||||
_profile = remainingChild->_profile;
|
||||
|
||||
// Add our new event handler before revoking the old one.
|
||||
_connectionClosedToken = _control.ConnectionClosed({ this, &Pane::_ControlClosedHandler });
|
||||
|
||||
// Revoke the old event handlers. Remove both the handlers for the panes
|
||||
// themselves closing, and remove their handlers for their controls
|
||||
// closing. At this point, if the remaining child's control is closed,
|
||||
// they'll trigger only our event handler for the control's close.
|
||||
_firstChild->Closed(_firstClosedToken);
|
||||
_secondChild->Closed(_secondClosedToken);
|
||||
closedChild->_control.ConnectionClosed(closedChild->_connectionClosedToken);
|
||||
remainingChild->_control.ConnectionClosed(remainingChild->_connectionClosedToken);
|
||||
|
||||
// If either of our children was focused, we want to take that focus from
|
||||
// them.
|
||||
_lastFocused = _firstChild->_lastFocused || _secondChild->_lastFocused;
|
||||
|
||||
// Remove all the ui elements of our children. This'll make sure we can
|
||||
// re-attach the TermControl to our Grid.
|
||||
_firstChild->_root.Children().Clear();
|
||||
_secondChild->_root.Children().Clear();
|
||||
|
||||
// Reset our UI:
|
||||
_root.Children().Clear();
|
||||
_root.ColumnDefinitions().Clear();
|
||||
_root.RowDefinitions().Clear();
|
||||
_separatorRoot = { nullptr };
|
||||
|
||||
// Reattach the TermControl to our grid.
|
||||
_root.Children().Append(_control.GetControl());
|
||||
|
||||
if (_lastFocused)
|
||||
{
|
||||
_control.GetControl().Focus(FocusState::Programmatic);
|
||||
}
|
||||
|
||||
_splitState = SplitState::None;
|
||||
|
||||
// Release our children.
|
||||
_firstChild = nullptr;
|
||||
_secondChild = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// First stash away references to the old panes and their tokens
|
||||
const auto oldFirstToken = _firstClosedToken;
|
||||
const auto oldSecondToken = _secondClosedToken;
|
||||
const auto oldFirst = _firstChild;
|
||||
const auto oldSecond = _secondClosedToken;
|
||||
|
||||
// Steal all the state from our child
|
||||
_splitState = remainingChild->_splitState;
|
||||
_separatorRoot = remainingChild->_separatorRoot;
|
||||
_firstChild = remainingChild->_firstChild;
|
||||
_secondChild = remainingChild->_secondChild;
|
||||
|
||||
// Set up new close handlers on the children
|
||||
_SetupChildCloseHandlers();
|
||||
|
||||
// Revoke the old event handlers.
|
||||
_firstChild->Closed(_firstClosedToken);
|
||||
_secondChild->Closed(_secondClosedToken);
|
||||
|
||||
// Reset our UI:
|
||||
_root.Children().Clear();
|
||||
_root.ColumnDefinitions().Clear();
|
||||
_root.RowDefinitions().Clear();
|
||||
|
||||
// Copy the old UI over to our grid.
|
||||
// Start by copying the row/column definitions. Iterate over the
|
||||
// rows/cols, and remove each one from the old grid, and attach it to
|
||||
// our grid instead.
|
||||
while (remainingChild->_root.ColumnDefinitions().Size() > 0)
|
||||
{
|
||||
auto col = remainingChild->_root.ColumnDefinitions().GetAt(0);
|
||||
remainingChild->_root.ColumnDefinitions().RemoveAt(0);
|
||||
_root.ColumnDefinitions().Append(col);
|
||||
}
|
||||
while (remainingChild->_root.RowDefinitions().Size() > 0)
|
||||
{
|
||||
auto row = remainingChild->_root.RowDefinitions().GetAt(0);
|
||||
remainingChild->_root.RowDefinitions().RemoveAt(0);
|
||||
_root.RowDefinitions().Append(row);
|
||||
}
|
||||
|
||||
// Remove the child's UI elements from the child's grid, so we can
|
||||
// attach them to us instead.
|
||||
remainingChild->_root.Children().Clear();
|
||||
|
||||
_root.Children().Append(_firstChild->GetRootElement());
|
||||
_root.Children().Append(_separatorRoot);
|
||||
_root.Children().Append(_secondChild->GetRootElement());
|
||||
|
||||
|
||||
// If the closed child was focused, transfer the focus to it's first sibling.
|
||||
if (closedChild->_lastFocused)
|
||||
{
|
||||
_FocusFirstChild();
|
||||
}
|
||||
|
||||
// Release the pointers that the child was holding.
|
||||
remainingChild->_firstChild = nullptr;
|
||||
remainingChild->_secondChild = nullptr;
|
||||
remainingChild->_separatorRoot = { nullptr };
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Adds event handlers to our children to handle their close events.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_SetupChildCloseHandlers()
|
||||
{
|
||||
_firstClosedToken = _firstChild->Closed([this](){
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=](){
|
||||
_CloseChild(true);
|
||||
});
|
||||
});
|
||||
|
||||
_secondClosedToken = _secondChild->Closed([this](){
|
||||
_root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=](){
|
||||
_CloseChild(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Initializes our UI for a new split in this pane. Sets up row/column
|
||||
// definitions, and initializes the separator grid. Does nothing if our split
|
||||
// state is currently set to SplitState::None
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_CreateSplitContent()
|
||||
{
|
||||
if (_splitState == SplitState::Vertical)
|
||||
{
|
||||
// Create three columns in this grid: one for each pane, and one for the separator.
|
||||
auto separatorColDef = Controls::ColumnDefinition();
|
||||
separatorColDef.Width(GridLengthHelper::Auto());
|
||||
|
||||
_root.ColumnDefinitions().Append(Controls::ColumnDefinition{});
|
||||
_root.ColumnDefinitions().Append(separatorColDef);
|
||||
_root.ColumnDefinitions().Append(Controls::ColumnDefinition{});
|
||||
|
||||
// Create the pane separator
|
||||
_separatorRoot = Controls::Grid{};
|
||||
_separatorRoot.Width(PaneSeparatorSize);
|
||||
// NaN is the special value XAML uses for "Auto" sizing.
|
||||
_separatorRoot.Height(NAN);
|
||||
}
|
||||
else if (_splitState == SplitState::Horizontal)
|
||||
{
|
||||
// Create three rows in this grid: one for each pane, and one for the separator.
|
||||
auto separatorRowDef = Controls::RowDefinition();
|
||||
separatorRowDef.Height(GridLengthHelper::Auto());
|
||||
|
||||
_root.RowDefinitions().Append(Controls::RowDefinition{});
|
||||
_root.RowDefinitions().Append(separatorRowDef);
|
||||
_root.RowDefinitions().Append(Controls::RowDefinition{});
|
||||
|
||||
// Create the pane separator
|
||||
_separatorRoot = Controls::Grid{};
|
||||
_separatorRoot.Height(PaneSeparatorSize);
|
||||
// NaN is the special value XAML uses for "Auto" sizing.
|
||||
_separatorRoot.Width(NAN);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the row/column of our child UI elements, to match our current split type.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_ApplySplitDefinitions()
|
||||
{
|
||||
if (_splitState == SplitState::Vertical)
|
||||
{
|
||||
Controls::Grid::SetColumn(_firstChild->GetRootElement(), 0);
|
||||
Controls::Grid::SetColumn(_separatorRoot, 1);
|
||||
Controls::Grid::SetColumn(_secondChild->GetRootElement(), 2);
|
||||
|
||||
}
|
||||
else if (_splitState == SplitState::Horizontal)
|
||||
{
|
||||
Controls::Grid::SetRow(_firstChild->GetRootElement(), 0);
|
||||
Controls::Grid::SetRow(_separatorRoot, 1);
|
||||
Controls::Grid::SetRow(_secondChild->GetRootElement(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Method Description:
|
||||
// - Vertically split the focused pane in our tree of panes, and place the given
|
||||
// TermControl into the newly created pane. If we're the focused pane, then
|
||||
// we'll create two new children, and place them side-by-side in our Grid.
|
||||
// Arguments:
|
||||
// - profile: The profile GUID to associate with the newly created pane.
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::SplitVertical(const GUID& profile, const TermControl& control)
|
||||
{
|
||||
// If we're not the leaf, recurse into our children to split them.
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
if (_firstChild->_HasFocusedChild())
|
||||
{
|
||||
_firstChild->SplitVertical(profile, control);
|
||||
}
|
||||
else if (_secondChild->_HasFocusedChild())
|
||||
{
|
||||
_secondChild->SplitVertical(profile, control);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_DoSplit(SplitState::Vertical, profile, control);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Horizontally split the focused pane in our tree of panes, and place the given
|
||||
// TermControl into the newly created pane. If we're the focused pane, then
|
||||
// we'll create two new children, and place them side-by-side in our Grid.
|
||||
// Arguments:
|
||||
// - profile: The profile GUID to associate with the newly created pane.
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::SplitHorizontal(const GUID& profile, const TermControl& control)
|
||||
{
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
if (_firstChild->_HasFocusedChild())
|
||||
{
|
||||
_firstChild->SplitHorizontal(profile, control);
|
||||
}
|
||||
else if (_secondChild->_HasFocusedChild())
|
||||
{
|
||||
_secondChild->SplitHorizontal(profile, control);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_DoSplit(SplitState::Horizontal, profile, control);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Does the bulk of the work of creating a new split. Initializes our UI,
|
||||
// creates a new Pane to host the control, registers event handlers.
|
||||
// Arguments:
|
||||
// - splitType: what type of split we should create.
|
||||
// - profile: The profile GUID to associate with the newly created pane.
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_DoSplit(SplitState splitType, const GUID& profile, const TermControl& control)
|
||||
{
|
||||
// Lock the create/close lock so that another operation won't concurrently
|
||||
// modify our tree
|
||||
std::unique_lock lock{ _createCloseLock };
|
||||
|
||||
// revoke our handler - the child will take care of the control now.
|
||||
_control.ConnectionClosed(_connectionClosedToken);
|
||||
_connectionClosedToken.value = 0;
|
||||
|
||||
_splitState = splitType;
|
||||
|
||||
_CreateSplitContent();
|
||||
|
||||
// Remove any children we currently have. We can't add the existing
|
||||
// TermControl to a new grid until we do this.
|
||||
_root.Children().Clear();
|
||||
|
||||
// Create two new Panes
|
||||
// Move our control, guid into the first one.
|
||||
// Move the new guid, control into the second.
|
||||
_firstChild = std::make_shared<Pane>(_profile.value(), _control);
|
||||
_profile = std::nullopt;
|
||||
_control = { nullptr };
|
||||
_secondChild = std::make_shared<Pane>(profile, control);
|
||||
|
||||
_root.Children().Append(_firstChild->GetRootElement());
|
||||
_root.Children().Append(_separatorRoot);
|
||||
_root.Children().Append(_secondChild->GetRootElement());
|
||||
|
||||
_ApplySplitDefinitions();
|
||||
|
||||
// Register event handlers on our children to handle their Close events
|
||||
_SetupChildCloseHandlers();
|
||||
|
||||
_lastFocused = false;
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Pane, Closed, _closedHandlers, ConnectionClosedEventArgs);
|
||||
85
src/cascadia/TerminalApp/Pane.h
Normal file
85
src/cascadia/TerminalApp/Pane.h
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// Module Name:
|
||||
// - Pane.h
|
||||
//
|
||||
// Abstract:
|
||||
// - Panes are an abstraction by which the terminal can dislay multiple terminal
|
||||
// instances simultaneously in a single terminal window. While tabs allow for
|
||||
// a single terminal window to have many terminal sessions running
|
||||
// simultaneously within a single window, only one tab can be visible at a
|
||||
// time. Panes, on the other hand, allow a user to have many different
|
||||
// terminal sessions visible to the user within the context of a single window
|
||||
// at the same time. This can enable greater productivity from the user, as
|
||||
// they can see the output of one terminal window while working in another.
|
||||
// - See doc/cascadia/Panes.md for a detailed description.
|
||||
//
|
||||
// Author:
|
||||
// - Mike Griese (zadjii-msft) 16-May-2019
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
class Pane : public std::enable_shared_from_this<Pane>
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
enum class SplitState : int
|
||||
{
|
||||
None = 0,
|
||||
Vertical = 1,
|
||||
Horizontal = 2
|
||||
};
|
||||
|
||||
Pane(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control, const bool lastFocused = false);
|
||||
|
||||
std::shared_ptr<Pane> GetFocusedPane();
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl GetFocusedTerminalControl();
|
||||
std::optional<GUID> GetFocusedProfile();
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::Grid GetRootElement();
|
||||
|
||||
bool WasLastFocused() const noexcept;
|
||||
void UpdateFocus();
|
||||
|
||||
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
|
||||
|
||||
void SplitHorizontal(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
void SplitVertical(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
DECLARE_EVENT(Closed, _closedHandlers, winrt::Microsoft::Terminal::TerminalControl::ConnectionClosedEventArgs);
|
||||
|
||||
private:
|
||||
winrt::Windows::UI::Xaml::Controls::Grid _root{};
|
||||
winrt::Windows::UI::Xaml::Controls::Grid _separatorRoot{ nullptr };
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl _control{ nullptr };
|
||||
|
||||
std::shared_ptr<Pane> _firstChild{ nullptr };
|
||||
std::shared_ptr<Pane> _secondChild{ nullptr };
|
||||
SplitState _splitState{ SplitState::None };
|
||||
|
||||
bool _lastFocused{ false };
|
||||
std::optional<GUID> _profile{ std::nullopt };
|
||||
winrt::event_token _connectionClosedToken{ 0 };
|
||||
winrt::event_token _firstClosedToken{ 0 };
|
||||
winrt::event_token _secondClosedToken{ 0 };
|
||||
|
||||
std::shared_mutex _createCloseLock{};
|
||||
|
||||
bool _IsLeaf() const noexcept;
|
||||
bool _HasFocusedChild() const noexcept;
|
||||
void _SetupChildCloseHandlers();
|
||||
|
||||
void _DoSplit(SplitState splitType, const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
void _CreateSplitContent();
|
||||
void _ApplySplitDefinitions();
|
||||
|
||||
void _CloseChild(const bool closeFirst);
|
||||
|
||||
void _FocusFirstChild();
|
||||
void _ControlClosedHandler();
|
||||
};
|
||||
@ -6,41 +6,47 @@
|
||||
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
|
||||
Tab::Tab(GUID profile, winrt::Microsoft::Terminal::TerminalControl::TermControl control) :
|
||||
_control{ control },
|
||||
_focused{ false },
|
||||
_profile{ profile },
|
||||
_tabViewItem{ nullptr }
|
||||
static const int TabViewFontSize = 12;
|
||||
|
||||
Tab::Tab(const GUID& profile, const TermControl& control)
|
||||
{
|
||||
_rootPane = std::make_shared<Pane>(profile, control, true);
|
||||
|
||||
_rootPane->Closed([=]() {
|
||||
_closedHandlers();
|
||||
});
|
||||
|
||||
_MakeTabViewItem();
|
||||
}
|
||||
|
||||
Tab::~Tab()
|
||||
{
|
||||
// When we're destructed, winrt will automatically decrement the refcount
|
||||
// of our terminalcontrol.
|
||||
// Assuming that refcount hits 0, it'll destruct it on its own, including
|
||||
// calling Close on the terminal and connection.
|
||||
}
|
||||
|
||||
void Tab::_MakeTabViewItem()
|
||||
{
|
||||
_tabViewItem = ::winrt::Microsoft::UI::Xaml::Controls::TabViewItem{};
|
||||
const auto title = _control.Title();
|
||||
|
||||
_tabViewItem.Header(title);
|
||||
|
||||
_control.TitleChanged([=](auto newTitle){
|
||||
_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=](){
|
||||
_tabViewItem.Header(newTitle);
|
||||
});
|
||||
});
|
||||
_tabViewItem.FontSize(TabViewFontSize);
|
||||
}
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl Tab::GetTerminalControl()
|
||||
UIElement Tab::GetRootElement()
|
||||
{
|
||||
return _control;
|
||||
return _rootPane->GetRootElement();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns nullptr if no children of this tab were the last control to be
|
||||
// focused, or the TermControl that _was_ the last control to be focused (if
|
||||
// there was one).
|
||||
// - This control might not currently be focused, if the tab itself is not
|
||||
// currently focused.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - nullptr if no children were marked `_lastFocused`, else the TermControl
|
||||
// that was last focused.
|
||||
TermControl Tab::GetFocusedTerminalControl()
|
||||
{
|
||||
return _rootPane->GetFocusedTerminalControl();
|
||||
}
|
||||
|
||||
winrt::Microsoft::UI::Xaml::Controls::TabViewItem Tab::GetTabViewItem()
|
||||
@ -48,13 +54,28 @@ winrt::Microsoft::UI::Xaml::Controls::TabViewItem Tab::GetTabViewItem()
|
||||
return _tabViewItem;
|
||||
}
|
||||
|
||||
|
||||
bool Tab::IsFocused()
|
||||
// Method Description:
|
||||
// - Returns true if this is the currently focused tab. For any set of tabs,
|
||||
// there should only be one tab that is marked as focused, though each tab has
|
||||
// no control over the other tabs in the set.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff this tab is focused.
|
||||
bool Tab::IsFocused() const noexcept
|
||||
{
|
||||
return _focused;
|
||||
}
|
||||
|
||||
void Tab::SetFocused(bool focused)
|
||||
// Method Description:
|
||||
// - Updates our focus state. If we're gaining focus, make sure to transfer
|
||||
// focus to the last focused terminal control in our tree of controls.
|
||||
// Arguments:
|
||||
// - focused: our new focus state. If true, we should be focused. If false, we
|
||||
// should be unfocused.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::SetFocused(const bool focused)
|
||||
{
|
||||
_focused = focused;
|
||||
|
||||
@ -64,15 +85,89 @@ void Tab::SetFocused(bool focused)
|
||||
}
|
||||
}
|
||||
|
||||
GUID Tab::GetProfile() const noexcept
|
||||
// Method Description:
|
||||
// - Returns nullopt if no children of this tab were the last control to be
|
||||
// focused, or the GUID of the profile of the last control to be focused (if
|
||||
// there was one).
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - nullopt if no children of this tab were the last control to be
|
||||
// focused, else the GUID of the profile of the last control to be focused
|
||||
std::optional<GUID> Tab::GetFocusedProfile() const noexcept
|
||||
{
|
||||
return _profile;
|
||||
return _rootPane->GetFocusedProfile();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to update the settings of this tab's tree of panes.
|
||||
// Arguments:
|
||||
// - settings: The new TerminalSettings to apply to any matching controls
|
||||
// - profile: The GUID of the profile these settings should apply to.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::UpdateSettings(const TerminalSettings& settings, const GUID& profile)
|
||||
{
|
||||
_rootPane->UpdateSettings(settings, profile);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Focus the last focused control in our tree of panes.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::_Focus()
|
||||
{
|
||||
_focused = true;
|
||||
_control.GetControl().Focus(FocusState::Programmatic);
|
||||
|
||||
auto lastFocusedControl = _rootPane->GetFocusedTerminalControl();
|
||||
if (lastFocusedControl)
|
||||
{
|
||||
lastFocusedControl.GetControl().Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update the focus state of this tab's tree of panes. If one of the controls
|
||||
// under this tab is focused, then it will be marked as the last focused. If
|
||||
// there are no focused panes, then there will not be a last focused control
|
||||
// when this returns.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::UpdateFocus()
|
||||
{
|
||||
_rootPane->UpdateFocus();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Gets the title string of the last focused terminal control in our tree.
|
||||
// Returns the empty string if there is no such control.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the title string of the last focused terminal control in our tree.
|
||||
winrt::hstring Tab::GetFocusedTitle() const
|
||||
{
|
||||
const auto lastFocusedControl = _rootPane->GetFocusedTerminalControl();
|
||||
return lastFocusedControl ? lastFocusedControl.Title() : L"";
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Set the text on the TabViewItem for this tab.
|
||||
// Arguments:
|
||||
// - text: The new text string to use as the Header for our TabViewItem
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::SetTabText(const winrt::hstring& text)
|
||||
{
|
||||
// Copy the hstring, so we don't capture a dead reference
|
||||
winrt::hstring textCopy{ text };
|
||||
_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [text = std::move(textCopy), this](){
|
||||
_tabViewItem.Header(text);
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@ -83,10 +178,39 @@ void Tab::_Focus()
|
||||
// - delta: a number of lines to move the viewport relative to the current viewport.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::Scroll(int delta)
|
||||
void Tab::Scroll(const int delta)
|
||||
{
|
||||
_control.GetControl().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=](){
|
||||
const auto currentOffset = _control.GetScrollOffset();
|
||||
_control.KeyboardScrollViewport(currentOffset + delta);
|
||||
auto control = GetFocusedTerminalControl();
|
||||
control.GetControl().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [control, delta](){
|
||||
const auto currentOffset = control.GetScrollOffset();
|
||||
control.KeyboardScrollViewport(currentOffset + delta);
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Vertically split the focused pane in our tree of panes, and place the
|
||||
// given TermControl into the newly created pane.
|
||||
// Arguments:
|
||||
// - profile: The profile GUID to associate with the newly created pane.
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::AddVerticalSplit(const GUID& profile, TermControl& control)
|
||||
{
|
||||
_rootPane->SplitVertical(profile, control);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Horizontally split the focused pane in our tree of panes, and place the
|
||||
// given TermControl into the newly created pane.
|
||||
// Arguments:
|
||||
// - profile: The profile GUID to associate with the newly created pane.
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::AddHorizontalSplit(const GUID& profile, TermControl& control)
|
||||
{
|
||||
_rootPane->SplitHorizontal(profile, control);
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Tab, Closed, _closedHandlers, ConnectionClosedEventArgs);
|
||||
|
||||
@ -3,30 +3,40 @@
|
||||
|
||||
#pragma once
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
#include "Pane.h"
|
||||
|
||||
class Tab
|
||||
{
|
||||
|
||||
public:
|
||||
Tab(GUID profile, winrt::Microsoft::Terminal::TerminalControl::TermControl control);
|
||||
~Tab();
|
||||
Tab(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
winrt::Microsoft::UI::Xaml::Controls::TabViewItem GetTabViewItem();
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl GetTerminalControl();
|
||||
winrt::Windows::UI::Xaml::UIElement GetRootElement();
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl GetFocusedTerminalControl();
|
||||
std::optional<GUID> GetFocusedProfile() const noexcept;
|
||||
|
||||
bool IsFocused();
|
||||
void SetFocused(bool focused);
|
||||
bool IsFocused() const noexcept;
|
||||
void SetFocused(const bool focused);
|
||||
|
||||
GUID GetProfile() const noexcept;
|
||||
void Scroll(const int delta);
|
||||
void AddVerticalSplit(const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
void AddHorizontalSplit(const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
void Scroll(int delta);
|
||||
void UpdateFocus();
|
||||
|
||||
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
|
||||
winrt::hstring GetFocusedTitle() const;
|
||||
void SetTabText(const winrt::hstring& text);
|
||||
|
||||
DECLARE_EVENT(Closed, _closedHandlers, winrt::Microsoft::Terminal::TerminalControl::ConnectionClosedEventArgs);
|
||||
|
||||
private:
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl _control;
|
||||
bool _focused;
|
||||
GUID _profile;
|
||||
winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem;
|
||||
|
||||
std::shared_ptr<Pane> _rootPane{ nullptr };
|
||||
|
||||
bool _focused{ false };
|
||||
winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr };
|
||||
|
||||
void _MakeTabViewItem();
|
||||
void _Focus();
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Tab.h" />
|
||||
<ClInclude Include="Pane.h" />
|
||||
<ClInclude Include="ColorScheme.h" />
|
||||
<ClInclude Include="GlobalAppSettings.h" />
|
||||
<ClInclude Include="Profile.h" />
|
||||
@ -56,6 +57,7 @@
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Tab.cpp" />
|
||||
<ClCompile Include="Pane.cpp" />
|
||||
<ClCompile Include="ColorScheme.cpp" />
|
||||
<ClCompile Include="GlobalAppSettings.cpp" />
|
||||
<ClCompile Include="Profile.cpp" />
|
||||
|
||||
@ -266,7 +266,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|| imageSource.UriSource().RawUri() != imageUri.RawUri())
|
||||
{
|
||||
// Note that BitmapImage handles the image load asynchronously,
|
||||
// which is especially important since the image
|
||||
// which is especially important since the image
|
||||
// may well be both large and somewhere out on the
|
||||
// internet.
|
||||
Media::Imaging::BitmapImage image(imageUri);
|
||||
@ -359,12 +359,22 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Don't let anyone else do something to the buffer.
|
||||
auto lock = _terminal->LockForWriting();
|
||||
|
||||
if (_connection != nullptr)
|
||||
// Clear out the cursor timer, so it doesn't trigger again on us once we're destructed.
|
||||
if (_cursorTimer)
|
||||
{
|
||||
_cursorTimer.value().Stop();
|
||||
_cursorTimer = std::nullopt;
|
||||
}
|
||||
|
||||
if (_connection)
|
||||
{
|
||||
_connection.Close();
|
||||
}
|
||||
|
||||
_renderer->TriggerTeardown();
|
||||
if (_renderer)
|
||||
{
|
||||
_renderer->TriggerTeardown();
|
||||
}
|
||||
|
||||
_swapChainPanel = nullptr;
|
||||
_root = nullptr;
|
||||
@ -694,9 +704,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse)
|
||||
{
|
||||
// Ignore mouse events while the terminal does not have focus.
|
||||
// This prevents the user from selecting and copying text if they
|
||||
// click inside the current tab to refocus the terminal window.
|
||||
// Ignore mouse events while the terminal does not have focus.
|
||||
// This prevents the user from selecting and copying text if they
|
||||
// click inside the current tab to refocus the terminal window.
|
||||
if (!_focused)
|
||||
{
|
||||
args.Handled(true);
|
||||
@ -970,6 +980,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
|
||||
RoutedEventArgs const& /* args */)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_focused = true;
|
||||
|
||||
if (_cursorTimer.has_value())
|
||||
@ -984,6 +998,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
|
||||
RoutedEventArgs const& /* args */)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_focused = false;
|
||||
|
||||
if (_cursorTimer.has_value())
|
||||
@ -1055,7 +1073,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_BlinkCursor(Windows::Foundation::IInspectable const& /* sender */,
|
||||
Windows::Foundation::IInspectable const& /* e */)
|
||||
{
|
||||
if (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible())
|
||||
if ((_closing) || (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -1191,7 +1209,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - Scrolls the viewport of the terminal and updates the scroll bar accordingly
|
||||
// Arguments:
|
||||
// - viewTop: the viewTop to scroll to
|
||||
// The difference between this function and ScrollViewport is that this one also
|
||||
// The difference between this function and ScrollViewport is that this one also
|
||||
// updates the _scrollBar after the viewport scroll. The reason _scrollBar is not updated in
|
||||
// ScrollViewport is because ScrollViewport is being called by _ScrollbarChangeHandler
|
||||
void TermControl::KeyboardScrollViewport(int viewTop)
|
||||
@ -1346,7 +1364,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// don't necessarily include that state.
|
||||
// Return Value:
|
||||
// - a KeyModifiers value with flags set for each key that's pressed.
|
||||
Settings::KeyModifiers TermControl::_GetPressedModifierKeys() const{
|
||||
Settings::KeyModifiers TermControl::_GetPressedModifierKeys() const
|
||||
{
|
||||
CoreWindow window = CoreWindow::GetForCurrentThread();
|
||||
// DONT USE
|
||||
// != CoreVirtualKeyStates::None
|
||||
@ -1368,6 +1387,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
(shift ? Settings::KeyModifiers::Shift : Settings::KeyModifiers::None) };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if this control should close when its connection is closed.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff the control should close when the connection is closed.
|
||||
bool TermControl::ShouldCloseOnExit() const noexcept
|
||||
{
|
||||
return _settings.CloseOnExit();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Gets the corresponding viewport terminal position for the cursor
|
||||
// by excluding the padding and normalizing with the font size.
|
||||
@ -1385,11 +1415,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
static_cast<SHORT>(cursorPosition.X - _root.Padding().Left),
|
||||
static_cast<SHORT>(cursorPosition.Y - _root.Padding().Top)
|
||||
};
|
||||
|
||||
|
||||
const auto fontSize = _actualFont.GetSize();
|
||||
FAIL_FAST_IF(fontSize.X == 0);
|
||||
FAIL_FAST_IF(fontSize.Y == 0);
|
||||
|
||||
|
||||
// Normalize to terminal coordinates by using font size
|
||||
terminalPosition.X /= fontSize.X;
|
||||
terminalPosition.Y /= fontSize.Y;
|
||||
|
||||
@ -41,6 +41,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
hstring Title();
|
||||
void CopySelectionToClipboard(bool trimTrailingWhitespace);
|
||||
void Close();
|
||||
bool ShouldCloseOnExit() const noexcept;
|
||||
|
||||
void ScrollViewport(int viewTop);
|
||||
void KeyboardScrollViewport(int viewTop);
|
||||
|
||||
@ -33,6 +33,7 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
String Title { get; };
|
||||
void CopySelectionToClipboard(Boolean trimTrailingWhitespace);
|
||||
void Close();
|
||||
Boolean ShouldCloseOnExit { get; };
|
||||
|
||||
void ScrollViewport(Int32 viewTop);
|
||||
void KeyboardScrollViewport(Int32 viewTop);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user