Convert Tab to a WinRT type (#4350)

## Summary of the Pull Request
This PR will make the existing `Tab` class into a WinRT type. This will allow any XAML to simply bind to the `ObservableVector` of Tabs. 

This PR will be followed up with a future PR to change our TabView to use the ObservableVector, which will in turn eliminate the need for maintaining two vectors of Tabs. (We currently maintain `_tabs` in `TerminalPage` and we also maintain `TabView().TabViewItems()` at the same time as described here: #2740)

## References
#3922 

## PR Checklist
* [x] CLA signed.
* [x] Tests added/passed

## Detailed Description of the Pull Request / Additional comments
I've currently only exposed a Tab's Title and IconPath to keep things simple. I foresee XAML elements that bind to Tabs to only really need these two properties for displaying.

I've also converted `TerminalPage`'s `std::vector<std::shared_ptr> _tabs` into a `IObservableVector<winrt::TerminalPage::Tab> _tabs` just so that future PRs will have the ground set for binding to this vector of tabs.

## Validation Steps Performed
Played around with Tabs and Panes and all sorts of combinations of keybindings for interacting with tabs and dragging and whatnot, it all seemed fine! Tab Tests also all pass.
This commit is contained in:
Leon Liang 2020-02-04 13:51:11 -08:00 committed by GitHub
parent d3aa56cb36
commit 99a28f9e9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 656 additions and 509 deletions

View file

@ -887,7 +887,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SwitchToTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(2, myArgs.TabIndex());
VERIFY_ARE_EQUAL(static_cast<uint32_t>(2), myArgs.TabIndex());
}
{

View file

@ -115,10 +115,10 @@ namespace TerminalAppLocalTests
void TabTests::TryCreateTab()
{
// If you leave the Tab shared_ptr owned by the RunOnUIThread lambda, it
// If you leave the Tab ptr owned by the RunOnUIThread lambda, it
// will crash when the test tears down. Not totally clear why, but make
// sure it's owned outside the lambda
std::shared_ptr<Tab> newTab{ nullptr };
winrt::com_ptr<winrt::TerminalApp::implementation::Tab> newTab{ nullptr };
auto result = RunOnUIThread([&newTab]() {
// Try creating all of:
@ -135,7 +135,7 @@ namespace TerminalAppLocalTests
winrt::Microsoft::Terminal::TerminalControl::TermControl term{ settings, conn };
VERIFY_IS_NOT_NULL(term);
newTab = std::make_shared<Tab>(profileGuid, term);
newTab = winrt::make_self<winrt::TerminalApp::implementation::Tab>(profileGuid, term);
VERIFY_IS_NOT_NULL(newTab);
});

View file

@ -143,7 +143,7 @@ namespace winrt::TerminalApp::implementation
struct SwitchToTabArgs : public SwitchToTabArgsT<SwitchToTabArgs>
{
SwitchToTabArgs() = default;
GETSET_PROPERTY(int32_t, TabIndex, 0);
GETSET_PROPERTY(uint32_t, TabIndex, 0);
static constexpr std::string_view TabIndexKey{ "index" };
@ -163,7 +163,7 @@ namespace winrt::TerminalApp::implementation
auto args = winrt::make_self<SwitchToTabArgs>();
if (auto tabIndex{ json[JsonKey(TabIndexKey)] })
{
args->_TabIndex = tabIndex.asInt();
args->_TabIndex = tabIndex.asUInt();
}
return *args;
}

View file

@ -60,7 +60,7 @@ namespace TerminalApp
[default_interface] runtimeclass SwitchToTabArgs : IActionArgs
{
Int32 TabIndex { get; };
UInt32 TabIndex { get; };
};
[default_interface] runtimeclass ResizePaneArgs : IActionArgs

View file

@ -3,8 +3,10 @@
#include "pch.h"
#include "Tab.h"
#include "Tab.g.cpp"
#include "Utils.h"
using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Microsoft::Terminal::Settings;
@ -15,379 +17,408 @@ namespace winrt
namespace MUX = Microsoft::UI::Xaml;
}
Tab::Tab(const GUID& profile, const TermControl& control)
namespace winrt::TerminalApp::implementation
{
_rootPane = std::make_shared<Pane>(profile, control, true);
_rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) {
_ClosedHandlers(nullptr, nullptr);
});
_activePane = _rootPane;
_MakeTabViewItem();
}
void Tab::_MakeTabViewItem()
{
_tabViewItem = ::winrt::MUX::Controls::TabViewItem{};
}
UIElement Tab::GetRootElement()
{
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::GetActiveTerminalControl() const
{
return _activePane->GetTerminalControl();
}
winrt::MUX::Controls::TabViewItem Tab::GetTabViewItem()
{
return _tabViewItem;
}
// 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;
}
// 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;
if (_focused)
Tab::Tab(const GUID& profile, const TermControl& control)
{
_Focus();
}
}
_rootPane = std::make_shared<Pane>(profile, control, true);
// 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 _activePane->GetFocusedProfile();
}
_rootPane->Closed([=](auto&& /*s*/, auto&& /*e*/) {
_ClosedHandlers(nullptr, nullptr);
});
// Method Description:
// - Called after construction of a Tab object to bind event handlers to its
// associated Pane and TermControl object
// Arguments:
// - control: reference to the TermControl object to bind event to
// Return Value:
// - <none>
void Tab::BindEventHandlers(const TermControl& control) noexcept
{
_AttachEventHandlersToPane(_rootPane);
_AttachEventHandlersToControl(control);
}
_activePane = _rootPane;
// 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;
auto lastFocusedControl = GetActiveTerminalControl();
if (lastFocusedControl)
{
lastFocusedControl.Focus(FocusState::Programmatic);
}
}
winrt::fire_and_forget Tab::UpdateIcon(const winrt::hstring iconPath)
{
// Don't reload our icon if it hasn't changed.
if (iconPath == _lastIconPath)
{
return;
_MakeTabViewItem();
}
_lastIconPath = iconPath;
std::weak_ptr<Tab> weakThis{ shared_from_this() };
co_await winrt::resume_foreground(_tabViewItem.Dispatcher());
if (auto tab{ weakThis.lock() })
// Method Description:
// - Initializes a TabViewItem for this Tab instance.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_MakeTabViewItem()
{
_tabViewItem.IconSource(GetColoredIcon<winrt::MUX::Controls::IconSource>(_lastIconPath));
_tabViewItem = ::winrt::MUX::Controls::TabViewItem{};
}
}
// 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::GetActiveTitle() const
{
const auto lastFocusedControl = GetActiveTerminalControl();
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>
winrt::fire_and_forget Tab::SetTabText(const winrt::hstring text)
{
// Copy the hstring, so we don't capture a dead reference
winrt::hstring textCopy{ text };
std::weak_ptr<Tab> weakThis{ shared_from_this() };
co_await winrt::resume_foreground(_tabViewItem.Dispatcher());
if (auto tab{ weakThis.lock() })
// Method Description:
// - Get the root UIElement of this Tab's root pane.
// Arguments:
// - <none>
// Return Value:
// - The UIElement acting as root of the Tab's root pane.
UIElement Tab::GetRootElement()
{
_tabViewItem.Header(winrt::box_value(text));
return _rootPane->GetRootElement();
}
}
// Method Description:
// - Move the viewport of the terminal up or down a number of lines. Negative
// values of `delta` will move the view up, and positive values will move
// the viewport down.
// Arguments:
// - delta: a number of lines to move the viewport relative to the current viewport.
// Return Value:
// - <none>
winrt::fire_and_forget Tab::Scroll(const int delta)
{
auto control = GetActiveTerminalControl();
// 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::GetActiveTerminalControl() const
{
return _activePane->GetTerminalControl();
}
co_await winrt::resume_foreground(control.Dispatcher());
// Method Description:
// - Gets the TabViewItem that represents this Tab
// Arguments:
// - <none>
// Return Value:
// - The TabViewItem that represents this Tab
winrt::MUX::Controls::TabViewItem Tab::GetTabViewItem()
{
return _tabViewItem;
}
const auto currentOffset = control.GetScrollOffset();
control.KeyboardScrollViewport(currentOffset + delta);
}
// 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;
}
// Method Description:
// - Determines whether the focused pane has sufficient space to be split.
// Arguments:
// - splitType: The type of split we want to create.
// Return Value:
// - True if the focused pane can be split. False otherwise.
bool Tab::CanSplitPane(winrt::TerminalApp::SplitState splitType)
{
return _activePane->CanSplit(splitType);
}
// 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;
// Method Description:
// - Split the focused pane in our tree of panes, and place the
// given TermControl into the newly created pane.
// Arguments:
// - splitType: The type of split we want to 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 Tab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, TermControl& control)
{
auto [first, second] = _activePane->Split(splitType, profile, control);
_activePane = first;
_AttachEventHandlersToControl(control);
// Add a event handlers to the new panes' GotFocus event. When the pane
// gains focus, we'll mark it as the new active pane.
_AttachEventHandlersToPane(first);
_AttachEventHandlersToPane(second);
}
// Method Description:
// - See Pane::CalcSnappedDimension
float Tab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
{
return _rootPane->CalcSnappedDimension(widthOrHeight, dimension);
}
// Method Description:
// - Update the size of our panes to fill the new given size. This happens when
// the window is resized.
// Arguments:
// - newSize: the amount of space that the panes have to fill now.
// Return Value:
// - <none>
void Tab::ResizeContent(const winrt::Windows::Foundation::Size& newSize)
{
// NOTE: This _must_ be called on the root pane, so that it can propogate
// throughout the entire tree.
_rootPane->ResizeContent(newSize);
}
// Method Description:
// - Attempt to move a separator between panes, as to resize each child on
// either size of the separator. See Pane::ResizePane for details.
// Arguments:
// - direction: The direction to move the separator in.
// Return Value:
// - <none>
void Tab::ResizePane(const winrt::TerminalApp::Direction& direction)
{
// NOTE: This _must_ be called on the root pane, so that it can propogate
// throughout the entire tree.
_rootPane->ResizePane(direction);
}
// Method Description:
// - Attempt to move focus between panes, as to focus the child on
// the other side of the separator. See Pane::NavigateFocus for details.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - <none>
void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction)
{
// NOTE: This _must_ be called on the root pane, so that it can propogate
// throughout the entire tree.
_rootPane->NavigateFocus(direction);
}
// Method Description:
// - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections.
void Tab::Shutdown()
{
_rootPane->Shutdown();
}
// Method Description:
// - Closes the currently focused pane in this tab. If it's the last pane in
// this tab, our Closed event will be fired (at a later time) for anyone
// registered as a handler of our close event.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::ClosePane()
{
_activePane->Close();
}
// Method Description:
// - Register any event handlers that we may need with the given TermControl.
// This should be called on each and every TermControl that we add to the tree
// of Panes in this tab. We'll add events too:
// * notify us when the control's title changed, so we can update our own
// title (if necessary)
// Arguments:
// - control: the TermControl to add events to.
// Return Value:
// - <none>
void Tab::_AttachEventHandlersToControl(const TermControl& control)
{
std::weak_ptr<Tab> weakThis{ shared_from_this() };
control.TitleChanged([weakThis](auto newTitle) {
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.lock() })
if (_focused)
{
// The title of the control changed, but not necessarily the title of the tab.
// Set the tab's text to the active panes' text.
tab->SetTabText(tab->GetActiveTitle());
_Focus();
}
});
}
// This is called when the terminal changes its font size or sets it for the first
// time (because when we just create terminal via its ctor it has invalid font size).
// On the latter event, we tell the root pane to resize itself so that its descendants
// (including ourself) can properly snap to character grids. In future, we may also
// want to do that on regular font changes.
control.FontSizeChanged([this](const int /* fontWidth */,
const int /* fontHeight */,
const bool isInitialChange) {
if (isInitialChange)
// 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 _activePane->GetFocusedProfile();
}
// Method Description:
// - Called after construction of a Tab object to bind event handlers to its
// associated Pane and TermControl object
// Arguments:
// - control: reference to the TermControl object to bind event to
// Return Value:
// - <none>
void Tab::BindEventHandlers(const TermControl& control) noexcept
{
_AttachEventHandlersToPane(_rootPane);
_AttachEventHandlersToControl(control);
}
// 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;
auto lastFocusedControl = GetActiveTerminalControl();
if (lastFocusedControl)
{
_rootPane->Relayout();
lastFocusedControl.Focus(FocusState::Programmatic);
}
});
}
}
// Method Description:
// - Add an event handler to this pane's GotFocus event. When that pane gains
// focus, we'll mark it as the new active pane. We'll also query the title of
// that pane when it's focused to set our own text, and finally, we'll trigger
// our own ActivePaneChanged event.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_AttachEventHandlersToPane(std::shared_ptr<Pane> pane)
{
std::weak_ptr<Tab> weakThis{ shared_from_this() };
pane->GotFocus([weakThis](std::shared_ptr<Pane> sender) {
// Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.lock() };
if (tab && sender != tab->_activePane)
// Method Description:
// - Set the icon on the TabViewItem for this tab.
// Arguments:
// - iconPath: The new path string to use as the IconPath for our TabViewItem
// Return Value:
// - <none>
winrt::fire_and_forget Tab::UpdateIcon(const winrt::hstring iconPath)
{
// Don't reload our icon if it hasn't changed.
if (iconPath == _lastIconPath)
{
// Clear the active state of the entire tree, and mark only the sender as active.
tab->_rootPane->ClearActive();
tab->_activePane = sender;
tab->_activePane->SetActive();
// Update our own title text to match the newly-active pane.
tab->SetTabText(tab->GetActiveTitle());
// Raise our own ActivePaneChanged event.
tab->_ActivePaneChangedHandlers();
return;
}
});
}
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
_lastIconPath = iconPath;
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_tabViewItem.Dispatcher());
if (auto tab{ weakThis.get() })
{
IconPath(_lastIconPath);
_tabViewItem.IconSource(GetColoredIcon<winrt::MUX::Controls::IconSource>(_lastIconPath));
}
}
// 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::GetActiveTitle() const
{
const auto lastFocusedControl = GetActiveTerminalControl();
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>
winrt::fire_and_forget Tab::SetTabText(const winrt::hstring text)
{
// Copy the hstring, so we don't capture a dead reference
winrt::hstring textCopy{ text };
auto weakThis{ get_weak() };
co_await winrt::resume_foreground(_tabViewItem.Dispatcher());
if (auto tab{ weakThis.get() })
{
Title(text);
_tabViewItem.Header(winrt::box_value(text));
}
}
// Method Description:
// - Move the viewport of the terminal up or down a number of lines. Negative
// values of `delta` will move the view up, and positive values will move
// the viewport down.
// Arguments:
// - delta: a number of lines to move the viewport relative to the current viewport.
// Return Value:
// - <none>
winrt::fire_and_forget Tab::Scroll(const int delta)
{
auto control = GetActiveTerminalControl();
co_await winrt::resume_foreground(control.Dispatcher());
const auto currentOffset = control.GetScrollOffset();
control.KeyboardScrollViewport(currentOffset + delta);
}
// Method Description:
// - Determines whether the focused pane has sufficient space to be split.
// Arguments:
// - splitType: The type of split we want to create.
// Return Value:
// - True if the focused pane can be split. False otherwise.
bool Tab::CanSplitPane(winrt::TerminalApp::SplitState splitType)
{
return _activePane->CanSplit(splitType);
}
// Method Description:
// - Split the focused pane in our tree of panes, and place the
// given TermControl into the newly created pane.
// Arguments:
// - splitType: The type of split we want to 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 Tab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, TermControl& control)
{
auto [first, second] = _activePane->Split(splitType, profile, control);
_activePane = first;
_AttachEventHandlersToControl(control);
// Add a event handlers to the new panes' GotFocus event. When the pane
// gains focus, we'll mark it as the new active pane.
_AttachEventHandlersToPane(first);
_AttachEventHandlersToPane(second);
}
// Method Description:
// - See Pane::CalcSnappedDimension
float Tab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
{
return _rootPane->CalcSnappedDimension(widthOrHeight, dimension);
}
// Method Description:
// - Update the size of our panes to fill the new given size. This happens when
// the window is resized.
// Arguments:
// - newSize: the amount of space that the panes have to fill now.
// Return Value:
// - <none>
void Tab::ResizeContent(const winrt::Windows::Foundation::Size& newSize)
{
// NOTE: This _must_ be called on the root pane, so that it can propogate
// throughout the entire tree.
_rootPane->ResizeContent(newSize);
}
// Method Description:
// - Attempt to move a separator between panes, as to resize each child on
// either size of the separator. See Pane::ResizePane for details.
// Arguments:
// - direction: The direction to move the separator in.
// Return Value:
// - <none>
void Tab::ResizePane(const winrt::TerminalApp::Direction& direction)
{
// NOTE: This _must_ be called on the root pane, so that it can propogate
// throughout the entire tree.
_rootPane->ResizePane(direction);
}
// Method Description:
// - Attempt to move focus between panes, as to focus the child on
// the other side of the separator. See Pane::NavigateFocus for details.
// Arguments:
// - direction: The direction to move the focus in.
// Return Value:
// - <none>
void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction)
{
// NOTE: This _must_ be called on the root pane, so that it can propogate
// throughout the entire tree.
_rootPane->NavigateFocus(direction);
}
// Method Description:
// - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections.
void Tab::Shutdown()
{
_rootPane->Shutdown();
}
// Method Description:
// - Closes the currently focused pane in this tab. If it's the last pane in
// this tab, our Closed event will be fired (at a later time) for anyone
// registered as a handler of our close event.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::ClosePane()
{
_activePane->Close();
}
// Method Description:
// - Register any event handlers that we may need with the given TermControl.
// This should be called on each and every TermControl that we add to the tree
// of Panes in this tab. We'll add events too:
// * notify us when the control's title changed, so we can update our own
// title (if necessary)
// Arguments:
// - control: the TermControl to add events to.
// Return Value:
// - <none>
void Tab::_AttachEventHandlersToControl(const TermControl& control)
{
auto weakThis{ get_weak() };
control.TitleChanged([weakThis](auto newTitle) {
// Check if Tab's lifetime has expired
if (auto tab{ weakThis.get() })
{
// The title of the control changed, but not necessarily the title of the tab.
// Set the tab's text to the active panes' text.
tab->SetTabText(tab->GetActiveTitle());
}
});
// This is called when the terminal changes its font size or sets it for the first
// time (because when we just create terminal via its ctor it has invalid font size).
// On the latter event, we tell the root pane to resize itself so that its descendants
// (including ourself) can properly snap to character grids. In future, we may also
// want to do that on regular font changes.
control.FontSizeChanged([this](const int /* fontWidth */,
const int /* fontHeight */,
const bool isInitialChange) {
if (isInitialChange)
{
_rootPane->Relayout();
}
});
}
// Method Description:
// - Add an event handler to this pane's GotFocus event. When that pane gains
// focus, we'll mark it as the new active pane. We'll also query the title of
// that pane when it's focused to set our own text, and finally, we'll trigger
// our own ActivePaneChanged event.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_AttachEventHandlersToPane(std::shared_ptr<Pane> pane)
{
auto weakThis{ get_weak() };
pane->GotFocus([weakThis](std::shared_ptr<Pane> sender) {
// Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.get() };
if (tab && sender != tab->_activePane)
{
// Clear the active state of the entire tree, and mark only the sender as active.
tab->_rootPane->ClearActive();
tab->_activePane = sender;
tab->_activePane->SetActive();
// Update our own title text to match the newly-active pane.
tab->SetTabText(tab->GetActiveTitle());
// Raise our own ActivePaneChanged event.
tab->_ActivePaneChangedHandlers();
}
});
}
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
}

View file

@ -3,57 +3,66 @@
#pragma once
#include "Pane.h"
#include "Tab.g.h"
class Tab : public std::enable_shared_from_this<Tab>
namespace winrt::TerminalApp::implementation
{
public:
Tab(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
struct Tab : public TabT<Tab>
{
public:
Tab() = delete;
Tab(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
// Called after construction to setup events with weak_ptr
void BindEventHandlers(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) noexcept;
// Called after construction to setup events with weak_ptr
void BindEventHandlers(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) noexcept;
winrt::Microsoft::UI::Xaml::Controls::TabViewItem GetTabViewItem();
winrt::Windows::UI::Xaml::UIElement GetRootElement();
winrt::Microsoft::Terminal::TerminalControl::TermControl GetActiveTerminalControl() const;
std::optional<GUID> GetFocusedProfile() const noexcept;
winrt::Microsoft::UI::Xaml::Controls::TabViewItem GetTabViewItem();
winrt::Windows::UI::Xaml::UIElement GetRootElement();
winrt::Microsoft::Terminal::TerminalControl::TermControl GetActiveTerminalControl() const;
std::optional<GUID> GetFocusedProfile() const noexcept;
bool IsFocused() const noexcept;
void SetFocused(const bool focused);
bool IsFocused() const noexcept;
void SetFocused(const bool focused);
winrt::fire_and_forget Scroll(const int delta);
winrt::fire_and_forget Scroll(const int delta);
bool CanSplitPane(winrt::TerminalApp::SplitState splitType);
void SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
bool CanSplitPane(winrt::TerminalApp::SplitState splitType);
void SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::TerminalApp::Direction& direction);
void NavigateFocus(const winrt::TerminalApp::Direction& direction);
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void ResizePane(const winrt::TerminalApp::Direction& direction);
void NavigateFocus(const winrt::TerminalApp::Direction& direction);
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
winrt::hstring GetActiveTitle() const;
winrt::fire_and_forget SetTabText(const winrt::hstring text);
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings, const GUID& profile);
winrt::hstring GetActiveTitle() const;
winrt::fire_and_forget SetTabText(const winrt::hstring text);
void Shutdown();
void ClosePane();
void Shutdown();
void ClosePane();
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
private:
std::shared_ptr<Pane> _rootPane{ nullptr };
std::shared_ptr<Pane> _activePane{ nullptr };
winrt::hstring _lastIconPath{};
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, IconPath, _PropertyChangedHandlers);
bool _focused{ false };
winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr };
private:
std::shared_ptr<Pane> _rootPane{ nullptr };
std::shared_ptr<Pane> _activePane{ nullptr };
winrt::hstring _lastIconPath{};
void _MakeTabViewItem();
void _Focus();
bool _focused{ false };
winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr };
void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
};
void _MakeTabViewItem();
void _Focus();
void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
};
}

View file

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace TerminalApp
{
[default_interface] runtimeclass Tab : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
String Title { get; };
String IconPath { get; };
}
}

View file

@ -35,6 +35,7 @@
<ClInclude Include="TitlebarControl.h" />
<ClInclude Include="TabRowControl.h" />
<ClInclude Include="App.h" />
<ClInclude Include="Tab.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>

View file

@ -39,7 +39,7 @@ namespace winrt
namespace winrt::TerminalApp::implementation
{
TerminalPage::TerminalPage() :
_tabs{}
_tabs{ winrt::single_threaded_observable_vector<TerminalApp::Tab>() }
{
InitializeComponent();
}
@ -81,9 +81,9 @@ namespace winrt::TerminalApp::implementation
if (from.has_value() && to.has_value() && to != from)
{
auto& tabs{ page->_tabs };
auto tab = tabs.at(from.value());
tabs.erase(tabs.begin() + from.value());
tabs.insert(tabs.begin() + to.value(), tab);
auto tab = tabs.GetAt(from.value());
tabs.RemoveAt(from.value());
tabs.InsertAt(to.value(), tab);
}
page->_rearranging = false;
@ -472,7 +472,7 @@ namespace winrt::TerminalApp::implementation
_CreateNewTabFromSettings(profileGuid, settings);
const int tabCount = static_cast<int>(_tabs.size());
const uint32_t tabCount = _tabs.Size();
const bool usedManualProfile = (newTerminalArgs != nullptr) &&
(newTerminalArgs.ProfileIndex() != nullptr ||
newTerminalArgs.Profile().empty());
@ -480,7 +480,7 @@ namespace winrt::TerminalApp::implementation
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
"TabInformation",
TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"),
TraceLoggingInt32(tabCount, "TabCount", "Count of tabs curently opened in TerminalApp"),
TraceLoggingUInt32(tabCount, "TabCount", "Count of tabs curently opened in TerminalApp"),
TraceLoggingBool(usedManualProfile, "ProfileSpecified", "Whether the new tab specified a profile explicitly"),
TraceLoggingGuid(profileGuid, "ProfileGuid", "The GUID of the profile spawned in the new tab"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
@ -501,7 +501,7 @@ namespace winrt::TerminalApp::implementation
// - settings: the TerminalSettings object to use to create the TerminalControl with.
void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings)
{
const bool isFirstTab = _tabs.empty();
const bool isFirstTab = _tabs.Size() == 0;
// Initialize the new tab
// Create a connection based on the values in our settings object.
@ -510,45 +510,46 @@ namespace winrt::TerminalApp::implementation
TermControl term{ settings, connection };
// Add the new tab to the list of our tabs.
auto newTab = _tabs.emplace_back(std::make_shared<Tab>(profileGuid, term));
auto newTabImpl = winrt::make_self<Tab>(profileGuid, term);
_tabs.Append(*newTabImpl);
// Hookup our event handlers to the new terminal
_RegisterTerminalEvents(term, newTab);
_RegisterTerminalEvents(term, *newTabImpl);
// 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 = newTab;
auto weakTab = make_weak(newTabImpl);
// When the tab's active pane changes, we'll want to lookup a new icon
// for it, and possibly propogate the title up to the window.
newTab->ActivePaneChanged([weakTabPtr, weakThis{ get_weak() }]() {
newTabImpl->ActivePaneChanged([weakTab, weakThis{ get_weak() }]() {
auto page{ weakThis.get() };
auto tab{ weakTabPtr.lock() };
auto tab{ weakTab.get() };
if (page && tab)
{
// Possibly update the icon of the tab.
page->_UpdateTabIcon(tab);
page->_UpdateTabIcon(*tab);
// Possibly update the title of the tab, window to match the newly
// focused pane.
page->_UpdateTitle(tab);
page->_UpdateTitle(*tab);
}
});
auto tabViewItem = newTab->GetTabViewItem();
auto tabViewItem = newTabImpl->GetTabViewItem();
_tabView.TabItems().Append(tabViewItem);
// Set this tab's icon to the icon from the user's profile
const auto* const profile = _settings->FindProfile(profileGuid);
if (profile != nullptr && profile->HasIcon())
{
newTab->UpdateIcon(profile->GetExpandedIconPath());
newTabImpl->UpdateIcon(profile->GetExpandedIconPath());
}
tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabClick });
// When the tab is closed, remove it from our list of tabs.
newTab->Closed([tabViewItem, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
newTabImpl->Closed([tabViewItem, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
if (auto page{ weakThis.get() })
{
page->_RemoveOnCloseRoutine(tabViewItem, page);
@ -561,7 +562,7 @@ namespace winrt::TerminalApp::implementation
if (isFirstTab)
{
_tabContent.Children().Clear();
_tabContent.Children().Append(newTab->GetRootElement());
_tabContent.Children().Append(newTabImpl->GetRootElement());
}
else
{
@ -727,12 +728,12 @@ namespace winrt::TerminalApp::implementation
// TitleChanged event.
// Arguments:
// - tab: the Tab to update the title for.
void TerminalPage::_UpdateTitle(std::shared_ptr<Tab> tab)
void TerminalPage::_UpdateTitle(const Tab& tab)
{
auto newTabTitle = tab->GetActiveTitle();
auto newTabTitle = tab.GetActiveTitle();
if (_settings->GlobalSettings().GetShowTitleInTitlebar() &&
tab->IsFocused())
tab.IsFocused())
{
_titleChangeHandlers(*this, newTabTitle);
}
@ -743,20 +744,20 @@ namespace winrt::TerminalApp::implementation
// tab's icon to that icon.
// Arguments:
// - tab: the Tab to update the title for.
void TerminalPage::_UpdateTabIcon(std::shared_ptr<Tab> tab)
void TerminalPage::_UpdateTabIcon(Tab& tab)
{
const auto lastFocusedProfileOpt = tab->GetFocusedProfile();
const auto lastFocusedProfileOpt = tab.GetFocusedProfile();
if (lastFocusedProfileOpt.has_value())
{
const auto lastFocusedProfile = lastFocusedProfileOpt.value();
const auto* const matchingProfile = _settings->FindProfile(lastFocusedProfile);
if (matchingProfile)
{
tab->UpdateIcon(matchingProfile->GetExpandedIconPath());
tab.UpdateIcon(matchingProfile->GetExpandedIconPath());
}
else
{
tab->UpdateIcon({});
tab.UpdateIcon({});
}
}
}
@ -777,7 +778,7 @@ namespace winrt::TerminalApp::implementation
// show the tab bar.
const bool isVisible = (!_isFullscreen) &&
(_settings->GlobalSettings().GetShowTabsInTitlebar() ||
(_tabs.size() > 1) ||
(_tabs.Size() > 1) ||
_settings->GlobalSettings().GetAlwaysShowTabs());
// collapse/show the tabs themselves
@ -792,14 +793,15 @@ namespace winrt::TerminalApp::implementation
// - Duplicates the current focused tab
void TerminalPage::_DuplicateTabViewItem()
{
const int& focusedTabIndex = _GetFocusedTabIndex();
const auto& _tab = _tabs.at(focusedTabIndex);
const auto& profileGuid = _tab->GetFocusedProfile();
if (profileGuid.has_value())
if (auto index{ _GetFocusedTabIndex() })
{
const auto settings = _settings->BuildSettings(profileGuid.value());
_CreateNewTabFromSettings(profileGuid.value(), settings);
auto focusedTab = _GetStrongTabImpl(*index);
const auto& profileGuid = focusedTab->GetFocusedProfile();
if (profileGuid.has_value())
{
const auto settings = _settings->BuildSettings(profileGuid.value());
_CreateNewTabFromSettings(profileGuid.value(), settings);
}
}
}
@ -824,32 +826,35 @@ namespace winrt::TerminalApp::implementation
{
// Removing the tab from the collection should destroy its control and disconnect its connection,
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
auto iterator = _tabs.begin() + tabIndex;
(*iterator)->Shutdown();
auto tab{ _GetStrongTabImpl(tabIndex) };
tab->Shutdown();
_tabs.erase(iterator);
_tabs.RemoveAt(tabIndex);
_tabView.TabItems().RemoveAt(tabIndex);
// To close the window here, we need to close the hosting window.
if (_tabs.size() == 0)
if (_tabs.Size() == 0)
{
_lastTabClosedHandlers(*this, nullptr);
}
auto focusedTabIndex = _GetFocusedTabIndex();
if (gsl::narrow_cast<int>(tabIndex) == focusedTabIndex)
if (auto indexOpt{ _GetFocusedTabIndex() })
{
auto const tabCount = gsl::narrow_cast<decltype(focusedTabIndex)>(_tabs.size());
if (focusedTabIndex >= tabCount)
auto focusedTabIndex = *indexOpt;
if (tabIndex == focusedTabIndex)
{
focusedTabIndex = tabCount - 1;
}
else if (focusedTabIndex < 0)
{
focusedTabIndex = 0;
}
uint32_t tabCount = _tabs.Size();
if (focusedTabIndex >= tabCount)
{
focusedTabIndex = tabCount - 1;
}
else if (focusedTabIndex < 0)
{
focusedTabIndex = 0;
}
_SelectTab(focusedTabIndex);
_SelectTab(focusedTabIndex);
}
}
}
@ -862,7 +867,7 @@ namespace winrt::TerminalApp::implementation
// Arguments:
// - term: The newly created TermControl to connect the events for
// - hostingTab: The Tab that's hosting this TermControl instance
void TerminalPage::_RegisterTerminalEvents(TermControl term, std::shared_ptr<Tab> hostingTab)
void TerminalPage::_RegisterTerminalEvents(TermControl term, 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
@ -872,22 +877,20 @@ namespace winrt::TerminalApp::implementation
term.PasteFromClipboard({ this, &TerminalPage::_PasteFromClipboardHandler });
// Bind Tab events to the TermControl and the Tab's Pane
hostingTab->BindEventHandlers(term);
hostingTab.BindEventHandlers(term);
// 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([weakTabPtr, weakThis{ get_weak() }](auto newTitle) {
term.TitleChanged([weakTab{ hostingTab.get_weak() }, weakThis{ get_weak() }](auto newTitle) {
auto page{ weakThis.get() };
auto tab{ weakTabPtr.lock() };
auto tab{ weakTab.get() };
if (page && tab)
{
// 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.
page->_UpdateTitle(tab);
page->_UpdateTitle(*tab);
}
});
}
@ -896,13 +899,14 @@ namespace winrt::TerminalApp::implementation
// - Sets focus to the tab to the right or left the currently selected tab.
void TerminalPage::_SelectNextTab(const bool bMoveRight)
{
int focusedTabIndex = _GetFocusedTabIndex();
auto tabCount = _tabs.size();
// Wraparound math. By adding tabCount and then calculating modulo tabCount,
// we clamp the values to the range [0, tabCount) while still supporting moving
// leftward from 0 to tabCount - 1.
_SetFocusedTabIndex(
static_cast<int>((tabCount + focusedTabIndex + (bMoveRight ? 1 : -1)) % tabCount));
if (auto index{ _GetFocusedTabIndex() })
{
uint32_t tabCount = _tabs.Size();
// Wraparound math. By adding tabCount and then calculating modulo tabCount,
// we clamp the values to the range [0, tabCount) while still supporting moving
// leftward from 0 to tabCount - 1.
_SetFocusedTabIndex(((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount));
}
}
// Method Description:
@ -910,9 +914,9 @@ namespace winrt::TerminalApp::implementation
// is greater than the number of tabs we have.
// Return Value:
// true iff we were able to select that tab index, false otherwise
bool TerminalPage::_SelectTab(const int tabIndex)
bool TerminalPage::_SelectTab(const uint32_t tabIndex)
{
if (tabIndex >= 0 && tabIndex < gsl::narrow_cast<decltype(tabIndex)>(_tabs.size()))
if (tabIndex >= 0 && tabIndex < _tabs.Size())
{
_SetFocusedTabIndex(tabIndex);
return true;
@ -930,15 +934,24 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_MoveFocus(const Direction& direction)
{
const auto focusedTabIndex = _GetFocusedTabIndex();
_tabs[focusedTabIndex]->NavigateFocus(direction);
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
focusedTab->NavigateFocus(direction);
}
}
winrt::Microsoft::Terminal::TerminalControl::TermControl TerminalPage::_GetActiveControl()
{
int focusedTabIndex = _GetFocusedTabIndex();
auto focusedTab = _tabs.at(focusedTabIndex);
return focusedTab->GetActiveTerminalControl();
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
return focusedTab->GetActiveTerminalControl();
}
else
{
return nullptr;
}
}
// Method Description:
@ -946,7 +959,7 @@ namespace winrt::TerminalApp::implementation
// no tab is currently selected, returns -1.
// Return Value:
// - the index of the currently focused tab if there is one, else -1
int TerminalPage::_GetFocusedTabIndex() const
std::optional<uint32_t> TerminalPage::_GetFocusedTabIndex() const noexcept
{
// GH#1117: This is a workaround because _tabView.SelectedIndex()
// sometimes return incorrect result after removing some tabs
@ -955,10 +968,10 @@ namespace winrt::TerminalApp::implementation
{
return focusedIndex;
}
return -1;
return std::nullopt;
}
winrt::fire_and_forget TerminalPage::_SetFocusedTabIndex(int tabIndex)
winrt::fire_and_forget TerminalPage::_SetFocusedTabIndex(const uint32_t tabIndex)
{
// GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex)
// sometimes set focus to an incorrect tab after removing some tabs
@ -968,7 +981,7 @@ namespace winrt::TerminalApp::implementation
if (auto page{ weakThis.get() })
{
auto tab = _tabs.at(tabIndex);
auto tab{ _GetStrongTabImpl(tabIndex) };
_tabView.SelectedItem(tab->GetTabViewItem());
}
}
@ -977,8 +990,10 @@ namespace winrt::TerminalApp::implementation
// - Close the currently focused tab. Focus will move to the left, if possible.
void TerminalPage::_CloseFocusedTab()
{
uint32_t focusedTabIndex = _GetFocusedTabIndex();
_RemoveTabViewItemByIndex(focusedTabIndex);
if (auto index{ _GetFocusedTabIndex() })
{
_RemoveTabViewItemByIndex(*index);
}
}
// Method Description:
@ -987,9 +1002,11 @@ namespace winrt::TerminalApp::implementation
// tab's Closed event.
void TerminalPage::_CloseFocusedPane()
{
int focusedTabIndex = _GetFocusedTabIndex();
std::shared_ptr<Tab> focusedTab{ _tabs[focusedTabIndex] };
focusedTab->ClosePane();
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
focusedTab->ClosePane();
}
}
// Method Description:
@ -997,7 +1014,7 @@ namespace winrt::TerminalApp::implementation
// than one tab opened, show a warning dialog.
void TerminalPage::CloseWindow()
{
if (_tabs.size() > 1 && _settings->GlobalSettings().GetConfirmCloseAllTabs())
if (_tabs.Size() > 1 && _settings->GlobalSettings().GetConfirmCloseAllTabs())
{
_ShowCloseWarningDialog();
}
@ -1012,7 +1029,7 @@ namespace winrt::TerminalApp::implementation
// on its own when the last tab is closed.
void TerminalPage::_CloseAllTabs()
{
while (!_tabs.empty())
while (_tabs.Size() != 0)
{
_RemoveTabViewItemByIndex(0);
}
@ -1026,8 +1043,11 @@ namespace winrt::TerminalApp::implementation
// - delta: a number of lines to move the viewport relative to the current viewport.
void TerminalPage::_Scroll(int delta)
{
int focusedTabIndex = _GetFocusedTabIndex();
_tabs[focusedTabIndex]->Scroll(delta);
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
focusedTab->Scroll(delta);
}
}
// Method Description:
@ -1049,13 +1069,20 @@ namespace winrt::TerminalApp::implementation
return;
}
auto indexOpt = _GetFocusedTabIndex();
// Do nothing if for some reason, there's no tab in focus. We don't want to crash.
if (!indexOpt)
{
return;
}
auto focusedTab = _GetStrongTabImpl(*indexOpt);
const auto [realGuid, controlSettings] = _settings->BuildSettings(newTerminalArgs);
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
const int focusedTabIndex = _GetFocusedTabIndex();
auto focusedTab = _tabs[focusedTabIndex];
const auto canSplit = focusedTab->CanSplitPane(splitType);
if (!canSplit)
@ -1066,7 +1093,7 @@ namespace winrt::TerminalApp::implementation
TermControl newControl{ controlSettings, controlConnection };
// Hookup our event handlers to the new terminal
_RegisterTerminalEvents(newControl, focusedTab);
_RegisterTerminalEvents(newControl, *focusedTab);
focusedTab->SplitPane(splitType, realGuid, newControl);
}
@ -1081,8 +1108,11 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_ResizePane(const Direction& direction)
{
const auto focusedTabIndex = _GetFocusedTabIndex();
_tabs[focusedTabIndex]->ResizePane(direction);
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
focusedTab->ResizePane(direction);
}
}
// Method Description:
@ -1095,11 +1125,18 @@ namespace winrt::TerminalApp::implementation
// is clamped between -1 and 1)
void TerminalPage::_ScrollPage(int delta)
{
auto indexOpt = _GetFocusedTabIndex();
// Do nothing if for some reason, there's no tab in focus. We don't want to crash.
if (!indexOpt)
{
return;
}
delta = std::clamp(delta, -1, 1);
const auto focusedTabIndex = _GetFocusedTabIndex();
const auto control = _GetActiveControl();
const auto termHeight = control.GetViewHeight();
_tabs[focusedTabIndex]->Scroll(termHeight * delta);
auto focusedTab{ _GetStrongTabImpl(*indexOpt) };
focusedTab->Scroll(termHeight * delta);
}
// Method Description:
@ -1208,13 +1245,13 @@ namespace winrt::TerminalApp::implementation
{
if (_settings->GlobalSettings().SnapToGridOnResize())
{
const auto focusedTabIndex = _GetFocusedTabIndex();
return _tabs[focusedTabIndex]->CalcSnappedDimension(widthOrHeight, dimension);
}
else
{
return dimension;
if (auto index{ _GetFocusedTabIndex() })
{
auto focusedTab{ _GetStrongTabImpl(*index) };
return focusedTab->CalcSnappedDimension(widthOrHeight, dimension);
}
}
return dimension;
}
// Method Description:
@ -1390,14 +1427,15 @@ namespace winrt::TerminalApp::implementation
// Unfocus all the tabs.
for (auto tab : _tabs)
{
tab->SetFocused(false);
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->SetFocused(false);
}
if (selectedIndex >= 0)
{
try
{
auto tab = _tabs.at(selectedIndex);
auto tab{ _GetStrongTabImpl(selectedIndex) };
_tabContent.Children().Clear();
_tabContent.Children().Append(tab->GetRootElement());
@ -1421,9 +1459,10 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_OnContentSizeChanged(const IInspectable& /*sender*/, Windows::UI::Xaml::SizeChangedEventArgs const& e)
{
const auto newSize = e.NewSize();
for (auto& tab : _tabs)
for (auto tab : _tabs)
{
tab->ResizeContent(newSize);
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->ResizeContent(newSize);
}
}
@ -1473,18 +1512,20 @@ namespace winrt::TerminalApp::implementation
const GUID profileGuid = profile.GetGuid();
const auto settings = _settings->BuildSettings(profileGuid);
for (auto& tab : _tabs)
for (auto tab : _tabs)
{
// Attempt to reload the settings of any panes with this profile
tab->UpdateSettings(settings, profileGuid);
auto tabImpl{ _GetStrongTabImpl(tab) };
tabImpl->UpdateSettings(settings, profileGuid);
}
}
// Update the icon of the tab for the currently focused profile in that tab.
for (auto& tab : _tabs)
for (auto tab : _tabs)
{
_UpdateTabIcon(tab);
_UpdateTitle(tab);
auto tabImpl{ _GetStrongTabImpl(tab) };
_UpdateTabIcon(*tabImpl);
_UpdateTitle(*tabImpl);
}
auto weakThis{ get_weak() };
@ -1627,6 +1668,32 @@ namespace winrt::TerminalApp::implementation
return winrt::to_hstring(_appArgs.GetExitMessage());
}
// Method Description:
// - Returns a com_ptr to the implementation type of the tab at the given index
// Arguments:
// - index: an unsigned integer index to a tab in _tabs
// Return Value:
// - a com_ptr to the implementation type of the Tab
winrt::com_ptr<Tab> TerminalPage::_GetStrongTabImpl(const uint32_t index) const
{
winrt::com_ptr<Tab> tabImpl;
tabImpl.copy_from(winrt::get_self<Tab>(_tabs.GetAt(index)));
return tabImpl;
}
// Method Description:
// - Returns a com_ptr to the implementation type of the given projected Tab
// Arguments:
// - tab: the projected type of a Tab
// Return Value:
// - a com_ptr to the implementation type of the Tab
winrt::com_ptr<Tab> TerminalPage::_GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const
{
winrt::com_ptr<Tab> tabImpl;
tabImpl.copy_from(winrt::get_self<Tab>(tab));
return tabImpl;
}
// -------------------------------- WinRT Events ---------------------------------
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.

View file

@ -63,7 +63,9 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<::TerminalApp::CascadiaSettings> _settings{ nullptr };
std::vector<std::shared_ptr<Tab>> _tabs;
Windows::Foundation::Collections::IObservableVector<TerminalApp::Tab> _tabs;
winrt::com_ptr<Tab> _GetStrongTabImpl(const uint32_t index) const;
winrt::com_ptr<Tab> _GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const;
bool _isFullscreen{ false };
@ -94,23 +96,23 @@ namespace winrt::TerminalApp::implementation
void _HookupKeyBindings(TerminalApp::AppKeyBindings bindings) noexcept;
void _RegisterActionCallbacks();
void _UpdateTitle(std::shared_ptr<Tab> tab);
void _UpdateTabIcon(std::shared_ptr<Tab> tab);
void _UpdateTitle(const Tab& tab);
void _UpdateTabIcon(Tab& tab);
void _UpdateTabView();
void _UpdateTabWidthMode();
void _DuplicateTabViewItem();
void _RemoveTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem);
void _RemoveTabViewItemByIndex(uint32_t tabIndex);
void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, std::shared_ptr<Tab> hostingTab);
void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, Tab& hostingTab);
void _SelectNextTab(const bool bMoveRight);
bool _SelectTab(const int tabIndex);
bool _SelectTab(const uint32_t tabIndex);
void _MoveFocus(const Direction& direction);
winrt::Microsoft::Terminal::TerminalControl::TermControl _GetActiveControl();
int _GetFocusedTabIndex() const;
winrt::fire_and_forget _SetFocusedTabIndex(int tabIndex);
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
winrt::fire_and_forget _SetFocusedTabIndex(const uint32_t tabIndex);
void _CloseFocusedTab();
void _CloseFocusedPane();
void _CloseAllTabs();

View file

@ -77,7 +77,9 @@
<ClInclude Include="../TabRowControl.h">
<DependentUpon>../TabRowControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="../Tab.h" />
<ClInclude Include="../Tab.h">
<DependentUpon>../Tab.idl</DependentUpon>
</ClInclude>
<ClInclude Include="../Pane.h" />
<ClInclude Include="../ColorScheme.h" />
<ClInclude Include="../GlobalAppSettings.h" />
@ -131,7 +133,9 @@
<ClCompile Include="../TabRowControl.cpp">
<DependentUpon>../TabRowControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="../Tab.cpp" />
<ClCompile Include="../Tab.cpp">
<DependentUpon>../Tab.idl</DependentUpon>
</ClCompile>
<ClCompile Include="../Pane.cpp" />
<ClCompile Include="../ColorScheme.cpp" />
<ClCompile Include="../GlobalAppSettings.cpp" />
@ -205,6 +209,7 @@
<DependentUpon>../TabRowControl.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="../Tab.idl"/>
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>
@ -349,4 +354,4 @@
<Target Name="_TerminalAppGenerateUserSettingsH" Inputs="..\userDefaults.json" Outputs="Generated Files\userDefaults.h" BeforeTargets="BeforeClCompile">
<Exec Command="powershell.exe -noprofile ExecutionPolicy Unrestricted $(OpenConsoleDir)\tools\GenerateHeaderForJson.ps1 -JsonFile ..\userDefaults.json -OutPath '&quot;Generated Files\userDefaults.h&quot;' -VariableName UserSettingsJson" />
</Target>
</Project>
</Project>

View file

@ -121,6 +121,9 @@
<Midl Include="../ShortcutActionDispatch.idl">
<Filter>settings</Filter>
</Midl>
<Midl Include="../Tab.idl">
<Filter>tab</Filter>
</Midl>
</ItemGroup>
<ItemGroup>
<None Include="../packages.config" />

View file

@ -107,6 +107,24 @@ public: \
private: \
type _##name{ __VA_ARGS__ };
// Use this macro to quickly implement both the getter and setter for an observable property.
// This is similar to the GETSET_PROPERTY macro above, except this will also raise a
// PropertyChanged event with the name of the property that has changed inside of the settter.
#define OBSERVABLE_GETSET_PROPERTY(type, name, event) \
public: \
type name() { return _##name; }; \
\
private: \
const type _##name; \
void name(const type& value) \
{ \
if (_##name != value) \
{ \
const_cast<type&>(_##name) = value; \
event(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L#name }); \
} \
};
// Use this macro for quickly defining the factory_implementation part of a
// class. CppWinrt requires these for the compiler, but more often than not,
// they require no customization. See