mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-11 04:38:24 -06:00
Enable dragging tabs between windows (#14901)
_Behold, the penultimate chapter in the saga of tear-out! This significant update bestows upon the user the power to transport tabs betwixt Terminal windows. Alas, the drag and drop capabilities of TabView are not yet refined, so this PR primarily concerns itself with the intricacies of plumbing. When a tab is extracted and deposited elsewhere, it is necessary to have the recipient make an inquiry to the Monarch, who in turn will beseech the sender to transmit the tab content, akin to the act of moving a tab. Curious it may seem, but the method has proven effective._ The penultimate tear-out PR. This PR enables the user to move tabs from one Terminal window to another. The TabView drag/drop APIs have some rough edges, so this PR is mostly plumbing. When a tab is drag/dropped, we need to get the recipient to ask the Monarch to ask the sender to send the tab content, like a MoveTab action. Wacky, but it works. There's a LONG tail of UX gaps. Those I'm going to track in #14900. It is more valuable for us to merge this now than to figure out workarounds immediately. The next PR will be the last main PR in this saga - in which we enable dragging a tab out of the window and dropping to create a new window. * Closes #1256 * Related to #5000 * Follow-ups get to go in #14900 ## Detailed description As I mentioned, it's mostly plumbing. The order that we get tab drag events is... unfortunate... for our use case. So we do a lot of sending `RequestReceiveContentArgs` up and down between windows, just to communicate who the tab was dropped on to whomever the tab was dragged from. There's a diagram for this that I originally put in https://github.com/microsoft/terminal/issues/5000#issuecomment-1435328038: ```mermaid sequenceDiagram participant Source participant Target participant Monarch Note Left of Source: _onTabDragStarting Source --> Source: stash dragged content Source --> Source: pack window ID into DataPackage Source ->> Target: Drag tab Note right of Target: _onTabStripDragOver Target ->> Target: AcceptedOperation(DataPackageOperation::Move) Source --> Target: Release mouse (to drop) Note right of Target: _onTabStripDrop Target --> Target: get WindowID from DataPackage Target -) Monarch: Request that WindowID sends content to us<br>RequestRecieveContent Monarch -) Source: Tell to send content to Target.Id<br>RequestSendContent, SendContent Source --> Source: detach our content Source -) Monarch: RequestMoveContent(stashed, target.id) Monarch -) Target: AttachContent(stashed) # Target -->> Source: # Note Left of Source: TabViewTabDragStartingEventArgs<br>.OperationCompleted # Note Left of Source: _onTabDroppedCompleted ``` Really really though, let's try to avoid nits about the UX at this time. This PR works with what we've got. Mail threads are percolating. I've got 19 chapters worth of Hobbit branch names to use for those follow ups.
This commit is contained in:
parent
17a5b77335
commit
9514c1191a
@ -1109,4 +1109,39 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// drag/drop)
|
||||
}
|
||||
}
|
||||
|
||||
// Very similar to the above. Someone came and told us that they were the target of a drag/drop, and they know who started it.
|
||||
// We will go tell the person who started it that they should send that target the content which was dragged.
|
||||
void Monarch::RequestSendContent(const Remoting::RequestReceiveContentArgs& args)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SendContent_Requested",
|
||||
TraceLoggingUInt64(args.SourceWindow(), "source", "The window which started the drag"),
|
||||
TraceLoggingUInt64(args.TargetWindow(), "target", "The window which was the target of the drop"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
if (auto senderPeasant{ _getPeasant(args.SourceWindow()) })
|
||||
{
|
||||
senderPeasant.SendContent(args);
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SendContent_Completed",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"Monarch_SendContent_NoWindow",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
// TODO GH#5000
|
||||
//
|
||||
// In the case where window couldn't be found, then create a window
|
||||
// for that name / ID. Do this as a part of tear-out (different than
|
||||
// drag/drop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +82,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
|
||||
|
||||
void RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex);
|
||||
void RequestSendContent(const Remoting::RequestReceiveContentArgs& args);
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
@ -41,8 +41,7 @@ namespace Microsoft.Terminal.Remoting
|
||||
Windows.Foundation.IReference<UInt64> WindowID;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass QuitAllRequestedArgs
|
||||
{
|
||||
[default_interface] runtimeclass QuitAllRequestedArgs {
|
||||
QuitAllRequestedArgs();
|
||||
Windows.Foundation.IAsyncAction BeforeQuitAllAction;
|
||||
}
|
||||
@ -71,6 +70,7 @@ namespace Microsoft.Terminal.Remoting
|
||||
Windows.Foundation.Collections.IVector<String> GetAllWindowLayouts();
|
||||
|
||||
void RequestMoveContent(String window, String content, UInt32 tabIndex);
|
||||
void RequestSendContent(RequestReceiveContentArgs args);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "Peasant.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
#include "AttachRequest.g.cpp"
|
||||
#include "RequestReceiveContentArgs.g.cpp"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
@ -327,4 +328,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
}
|
||||
return args->WindowLayoutJson();
|
||||
}
|
||||
|
||||
void Peasant::SendContent(const Remoting::RequestReceiveContentArgs& args)
|
||||
{
|
||||
_SendContentRequestedHandlers(*this, args);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "Peasant.g.h"
|
||||
#include "RenameRequestArgs.h"
|
||||
#include "AttachRequest.g.h"
|
||||
#include "RequestReceiveContentArgs.g.h"
|
||||
|
||||
namespace RemotingUnitTests
|
||||
{
|
||||
@ -25,6 +26,19 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
_TabIndex{ tabIndex } {};
|
||||
};
|
||||
|
||||
struct RequestReceiveContentArgs : RequestReceiveContentArgsT<RequestReceiveContentArgs>
|
||||
{
|
||||
WINRT_PROPERTY(uint64_t, SourceWindow);
|
||||
WINRT_PROPERTY(uint64_t, TargetWindow);
|
||||
WINRT_PROPERTY(uint32_t, TabIndex);
|
||||
|
||||
public:
|
||||
RequestReceiveContentArgs(const uint64_t src, const uint64_t tgt, const uint32_t tabIndex) :
|
||||
_SourceWindow{ src },
|
||||
_TargetWindow{ tgt },
|
||||
_TabIndex{ tabIndex } {};
|
||||
};
|
||||
|
||||
struct Peasant : public PeasantT<Peasant>
|
||||
{
|
||||
Peasant();
|
||||
@ -52,6 +66,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
|
||||
|
||||
winrt::hstring GetWindowLayout();
|
||||
void SendContent(const winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs& args);
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, WindowName);
|
||||
WINRT_PROPERTY(winrt::hstring, ActiveTabTitle);
|
||||
@ -70,6 +85,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::GetWindowLayoutArgs);
|
||||
|
||||
TYPED_EVENT(AttachRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::AttachRequest);
|
||||
TYPED_EVENT(SendContentRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs);
|
||||
|
||||
private:
|
||||
Peasant(const uint64_t testPID);
|
||||
@ -87,4 +103,5 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(Peasant);
|
||||
BASIC_FACTORY(RequestReceiveContentArgs);
|
||||
}
|
||||
|
||||
@ -55,6 +55,13 @@ namespace Microsoft.Terminal.Remoting
|
||||
String Content { get; };
|
||||
UInt32 TabIndex { get; };
|
||||
}
|
||||
[default_interface] runtimeclass RequestReceiveContentArgs {
|
||||
RequestReceiveContentArgs(UInt64 src, UInt64 tgt, UInt32 tabIndex);
|
||||
|
||||
UInt64 SourceWindow { get; };
|
||||
UInt64 TargetWindow { get; };
|
||||
UInt32 TabIndex { get; };
|
||||
};
|
||||
|
||||
interface IPeasant
|
||||
{
|
||||
@ -82,7 +89,7 @@ namespace Microsoft.Terminal.Remoting
|
||||
String GetWindowLayout();
|
||||
|
||||
void AttachContentToWindow(AttachRequest request);
|
||||
|
||||
void SendContent(RequestReceiveContentArgs args);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
|
||||
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
|
||||
@ -98,6 +105,8 @@ namespace Microsoft.Terminal.Remoting
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, AttachRequest> AttachRequested;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> SendContentRequested;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass Peasant : IPeasant
|
||||
|
||||
@ -422,4 +422,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
_monarch.RequestMoveContent(window, content, tabIndex);
|
||||
}
|
||||
|
||||
winrt::fire_and_forget WindowManager::RequestSendContent(Remoting::RequestReceiveContentArgs args)
|
||||
{
|
||||
co_await winrt::resume_background();
|
||||
_monarch.RequestSendContent(args);
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
bool DoesQuakeWindowExist();
|
||||
|
||||
winrt::fire_and_forget RequestMoveContent(winrt::hstring window, winrt::hstring content, uint32_t tabIndex);
|
||||
winrt::fire_and_forget RequestSendContent(Remoting::RequestReceiveContentArgs args);
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ namespace Microsoft.Terminal.Remoting
|
||||
Boolean DoesQuakeWindowExist();
|
||||
|
||||
void RequestMoveContent(String window, String content, UInt32 tabIndex);
|
||||
void RequestSendContent(RequestReceiveContentArgs args);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
|
||||
|
||||
@ -264,6 +264,19 @@ namespace winrt::TerminalApp::implementation
|
||||
tab->_RequestFocusActiveControlHandlers();
|
||||
}
|
||||
});
|
||||
|
||||
// BODGY: When the tab is drag/dropped, the TabView gets a
|
||||
// TabDragStarting. However, the way it is implemented[^1], the
|
||||
// TabViewItem needs either an Item or a Content for the event to
|
||||
// include the correct TabViewItem. Otherwise, it will just return the
|
||||
// first TabViewItem in the TabView with the same Content as the dragged
|
||||
// tab (which, if the Content is null, will be the _first_ tab).
|
||||
//
|
||||
// So here, we'll stick an empty border in, just so that every tab has a
|
||||
// Content which is not equal to the others.
|
||||
//
|
||||
// [^1]: microsoft-ui-xaml/blob/92fbfcd55f05c92ac65569f5d284c5b36492091e/dev/TabView/TabView.cpp#L751-L758
|
||||
TabViewItem().Content(winrt::WUX::Controls::Border{});
|
||||
}
|
||||
|
||||
std::optional<winrt::Windows::UI::Color> TabBase::GetTabColor()
|
||||
|
||||
@ -512,6 +512,11 @@ namespace winrt::TerminalApp::implementation
|
||||
_mruTabs.RemoveAt(mruIndex);
|
||||
}
|
||||
|
||||
if (_stashedDraggedTab && *_stashedDraggedTab == tab)
|
||||
{
|
||||
_stashedDraggedTab = nullptr;
|
||||
}
|
||||
|
||||
_tabs.RemoveAt(tabIndex);
|
||||
_tabView.TabItems().RemoveAt(tabIndex);
|
||||
_UpdateTabIndices();
|
||||
@ -703,7 +708,8 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::TerminalApp::TabBase TerminalPage::_GetTabByTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem) const noexcept
|
||||
{
|
||||
uint32_t tabIndexFromControl{};
|
||||
if (_tabView.TabItems().IndexOf(tabViewItem, tabIndexFromControl))
|
||||
const auto items{ _tabView.TabItems() };
|
||||
if (items.IndexOf(tabViewItem, tabIndexFromControl))
|
||||
{
|
||||
// If IndexOf returns true, we've actually got an index
|
||||
return _tabs.GetAt(tabIndexFromControl);
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "LastTabClosedEventArgs.g.cpp"
|
||||
#include "RenameWindowRequestedArgs.g.cpp"
|
||||
#include "RequestMoveContentArgs.g.cpp"
|
||||
#include "RequestReceiveContentArgs.g.cpp"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
@ -246,6 +247,10 @@ namespace winrt::TerminalApp::implementation
|
||||
_tabView.TabCloseRequested({ this, &TerminalPage::_OnTabCloseRequested });
|
||||
_tabView.TabItemsChanged({ this, &TerminalPage::_OnTabItemsChanged });
|
||||
|
||||
_tabView.TabDragStarting({ this, &TerminalPage::_onTabDragStarting });
|
||||
_tabView.TabStripDragOver({ this, &TerminalPage::_onTabStripDragOver });
|
||||
_tabView.TabStripDrop({ this, &TerminalPage::_onTabStripDrop });
|
||||
|
||||
_CreateNewTabFlyout();
|
||||
|
||||
_UpdateTabWidthMode();
|
||||
@ -1959,12 +1964,15 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
}
|
||||
|
||||
void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr<TerminalTab>& terminalTab)
|
||||
void TerminalPage::_DetachTabFromWindow(const winrt::com_ptr<TabBase>& tab)
|
||||
{
|
||||
// Detach the root pane, which will act like the whole tab got detached.
|
||||
if (const auto rootPane = terminalTab->GetRootPane())
|
||||
if (const auto terminalTab = tab.try_as<TerminalTab>())
|
||||
{
|
||||
_DetachPaneFromWindow(rootPane);
|
||||
// Detach the root pane, which will act like the whole tab got detached.
|
||||
if (const auto rootPane = terminalTab->GetRootPane())
|
||||
{
|
||||
_DetachPaneFromWindow(rootPane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2073,6 +2081,25 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
_actionDispatch->DoAction(action);
|
||||
}
|
||||
|
||||
// After handling all the actions, then re-check the tabIndex. We might
|
||||
// have been called as a part of a tab drag/drop. In that case, the
|
||||
// tabIndex is actually relevant, and we need to move the tab we just
|
||||
// made into position.
|
||||
if (!firstIsSplitPane && tabIndex != -1)
|
||||
{
|
||||
// Move the currently active tab to the requested index Use the
|
||||
// currently focused tab index, because we don't know if the new tab
|
||||
// opened at the end of the list, or adjacent to the previously
|
||||
// active tab. This is affected by the user's "newTabPosition"
|
||||
// setting.
|
||||
if (const auto focusedTabIndex = _GetFocusedTabIndex())
|
||||
{
|
||||
const auto source = *focusedTabIndex;
|
||||
_TryMoveTab(source, tabIndex);
|
||||
}
|
||||
// else: This shouldn't really be possible, because the tab we _just_ opened should be active.
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@ -4572,4 +4599,160 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView&,
|
||||
const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e)
|
||||
{
|
||||
// Get the tab impl from this event.
|
||||
const auto eventTab = e.Tab();
|
||||
const auto tabBase = _GetTabByTabViewItem(eventTab);
|
||||
winrt::com_ptr<TabBase> tabImpl;
|
||||
tabImpl.copy_from(winrt::get_self<TabBase>(tabBase));
|
||||
if (tabImpl)
|
||||
{
|
||||
// First: stash the tab we started dragging.
|
||||
// We're going to be asked for this.
|
||||
_stashedDraggedTab = tabImpl;
|
||||
|
||||
// Into the DataPackage, let's stash our own window ID.
|
||||
const auto id{ _WindowProperties.WindowId() };
|
||||
|
||||
// Get our PID
|
||||
const auto pid{ GetCurrentProcessId() };
|
||||
|
||||
e.Data().Properties().Insert(L"windowId", winrt::box_value(id));
|
||||
e.Data().Properties().Insert(L"pid", winrt::box_value<uint32_t>(pid));
|
||||
e.Data().RequestedOperation(DataPackageOperation::Move);
|
||||
|
||||
// The next thing that will happen:
|
||||
// * Another TerminalPage will get a TabStripDragOver, then get a
|
||||
// TabStripDrop
|
||||
// * This will be handled by the _other_ page asking the monarch
|
||||
// to ask us to send our content to them.
|
||||
// * We'll get a TabDroppedOutside to indicate that this tab was
|
||||
// dropped _not_ on a TabView.
|
||||
// * This we can't handle yet, and is the last point of TODO GH#5000
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_onTabStripDragOver(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Windows::UI::Xaml::DragEventArgs& e)
|
||||
{
|
||||
// We must mark that we can accept the drag/drop. The system will never
|
||||
// call TabStripDrop on us if we don't indicate that we're willing.
|
||||
const auto& props{ e.DataView().Properties() };
|
||||
if (props.HasKey(L"windowId") &&
|
||||
props.HasKey(L"pid") &&
|
||||
(winrt::unbox_value_or<uint32_t>(props.TryLookup(L"pid"), 0u) == GetCurrentProcessId()))
|
||||
{
|
||||
e.AcceptedOperation(DataPackageOperation::Move);
|
||||
}
|
||||
|
||||
// You may think to yourself, this is a great place to increase the
|
||||
// width of the TabView artificially, to make room for the new tab item.
|
||||
// However, we'll never get a message that the tab left the tab view
|
||||
// (without being dropped). So there's no good way to resize back down.
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called on the TARGET of a tab drag/drop. We'll unpack the DataPackage
|
||||
// to find who the tab came from. We'll then ask the Monarch to ask the
|
||||
// sender to move that tab to us.
|
||||
winrt::fire_and_forget TerminalPage::_onTabStripDrop(winrt::Windows::Foundation::IInspectable /*sender*/,
|
||||
winrt::Windows::UI::Xaml::DragEventArgs e)
|
||||
{
|
||||
// Get the PID and make sure it is the same as ours.
|
||||
if (const auto& pidObj{ e.DataView().Properties().TryLookup(L"pid") })
|
||||
{
|
||||
const auto pid{ winrt::unbox_value_or<uint32_t>(pidObj, 0u) };
|
||||
if (pid != GetCurrentProcessId())
|
||||
{
|
||||
// The PID doesn't match ours. We can't handle this drop.
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No PID? We can't handle this drop. Bail.
|
||||
co_return;
|
||||
}
|
||||
|
||||
const auto& windowIdObj{ e.DataView().Properties().TryLookup(L"windowId") };
|
||||
if (windowIdObj == nullptr)
|
||||
{
|
||||
// No windowId? Bail.
|
||||
co_return;
|
||||
}
|
||||
const uint64_t src{ winrt::unbox_value<uint64_t>(windowIdObj) };
|
||||
|
||||
// Figure out where in the tab strip we're dropping this tab. Add that
|
||||
// index to the request. This is largely taken from the WinUI sample
|
||||
// app.
|
||||
|
||||
// We need to be on OUR UI thread to figure out where we dropped
|
||||
auto weakThis{ get_weak() };
|
||||
co_await wil::resume_foreground(Dispatcher());
|
||||
if (const auto& page{ weakThis.get() })
|
||||
{
|
||||
// First we need to get the position in the List to drop to
|
||||
auto index = -1;
|
||||
|
||||
// Determine which items in the list our pointer is between.
|
||||
for (auto i = 0u; i < _tabView.TabItems().Size(); i++)
|
||||
{
|
||||
if (const auto& item{ _tabView.ContainerFromIndex(i).try_as<winrt::MUX::Controls::TabViewItem>() })
|
||||
{
|
||||
const auto posX{ e.GetPosition(item).X }; // The point of the drop, relative to the tab
|
||||
const auto itemWidth{ item.ActualWidth() }; // The right of the tab
|
||||
// If the drag point is on the left half of the tab, then insert here.
|
||||
if (posX < itemWidth / 2)
|
||||
{
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `this` is safe to use
|
||||
const auto request = winrt::make_self<RequestReceiveContentArgs>(src, _WindowProperties.WindowId(), index);
|
||||
|
||||
// This will go up to the monarch, who will then dispatch the request
|
||||
// back down to the source TerminalPage, who will then perform a
|
||||
// RequestMoveContent to move their tab to us.
|
||||
_RequestReceiveContentHandlers(*this, *request);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is called on the drag/drop SOURCE TerminalPage, when the monarch has
|
||||
// requested that we send our tab to another window. We'll need to
|
||||
// serialize the tab, and send it to the monarch, who will then send it to
|
||||
// the destination window.
|
||||
// - Fortunately, sending the tab is basically just a MoveTab action, so we
|
||||
// can largely reuse that.
|
||||
winrt::fire_and_forget TerminalPage::SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args)
|
||||
{
|
||||
// validate that we're the source window of the tab in this request
|
||||
if (args.SourceWindow() != _WindowProperties.WindowId())
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
if (!_stashedDraggedTab)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
// must do the work of adding/removing tabs on the UI thread.
|
||||
auto weakThis{ get_weak() };
|
||||
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
|
||||
if (const auto& page{ weakThis.get() })
|
||||
{
|
||||
// `this` is safe to use in here.
|
||||
auto startupActions = _stashedDraggedTab->BuildStartupActions(true);
|
||||
_DetachTabFromWindow(_stashedDraggedTab);
|
||||
_MoveContent(std::move(startupActions), winrt::hstring{ fmt::format(L"{}", args.TargetWindow()) }, args.TabIndex());
|
||||
// _RemoveTab will make sure to null out the _stashedDraggedTab
|
||||
_RemoveTab(*_stashedDraggedTab);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "LastTabClosedEventArgs.g.h"
|
||||
#include "RenameWindowRequestedArgs.g.h"
|
||||
#include "RequestMoveContentArgs.g.h"
|
||||
#include "RequestReceiveContentArgs.g.h"
|
||||
#include "Toast.h"
|
||||
|
||||
#define DECLARE_ACTION_HANDLER(action) void _Handle##action(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
|
||||
@ -74,6 +75,19 @@ namespace winrt::TerminalApp::implementation
|
||||
_TabIndex{ tabIndex } {};
|
||||
};
|
||||
|
||||
struct RequestReceiveContentArgs : RequestReceiveContentArgsT<RequestReceiveContentArgs>
|
||||
{
|
||||
WINRT_PROPERTY(uint64_t, SourceWindow);
|
||||
WINRT_PROPERTY(uint64_t, TargetWindow);
|
||||
WINRT_PROPERTY(uint32_t, TabIndex);
|
||||
|
||||
public:
|
||||
RequestReceiveContentArgs(const uint64_t src, const uint64_t tgt, const uint32_t tabIndex) :
|
||||
_SourceWindow{ src },
|
||||
_TargetWindow{ tgt },
|
||||
_TabIndex{ tabIndex } {};
|
||||
};
|
||||
|
||||
struct TerminalPage : TerminalPageT<TerminalPage>
|
||||
{
|
||||
public:
|
||||
@ -149,6 +163,7 @@ namespace winrt::TerminalApp::implementation
|
||||
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
|
||||
|
||||
winrt::fire_and_forget AttachContent(winrt::hstring content, uint32_t tabIndex);
|
||||
winrt::fire_and_forget SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
|
||||
@ -173,6 +188,7 @@ namespace winrt::TerminalApp::implementation
|
||||
TYPED_EVENT(ShowWindowChanged, IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs)
|
||||
|
||||
TYPED_EVENT(RequestMoveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMoveContentArgs);
|
||||
TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs);
|
||||
|
||||
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, _PropertyChangedHandlers, nullptr);
|
||||
|
||||
@ -247,6 +263,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
TerminalApp::ContentManager _manager{ nullptr };
|
||||
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TabBase> _stashedDraggedTab{ nullptr };
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker;
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);
|
||||
@ -482,8 +500,12 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
|
||||
winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
|
||||
|
||||
void _onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e);
|
||||
void _onTabStripDragOver(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::DragEventArgs& e);
|
||||
winrt::fire_and_forget _onTabStripDrop(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::DragEventArgs e);
|
||||
|
||||
void _DetachPaneFromWindow(std::shared_ptr<Pane> pane);
|
||||
void _DetachTabFromWindow(const winrt::com_ptr<TerminalTab>& terminalTab);
|
||||
void _DetachTabFromWindow(const winrt::com_ptr<TabBase>& terminalTab);
|
||||
void _MoveContent(std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>&& actions, const winrt::hstring& windowName, const uint32_t tabIndex);
|
||||
|
||||
void _ContextMenuOpened(const IInspectable& sender, const IInspectable& args);
|
||||
@ -505,4 +527,5 @@ namespace winrt::TerminalApp::implementation
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(TerminalPage);
|
||||
BASIC_FACTORY(RequestReceiveContentArgs);
|
||||
}
|
||||
|
||||
@ -30,6 +30,13 @@ namespace TerminalApp
|
||||
String Content { get; };
|
||||
UInt32 TabIndex { get; };
|
||||
};
|
||||
[default_interface] runtimeclass RequestReceiveContentArgs {
|
||||
RequestReceiveContentArgs(UInt64 src, UInt64 tgt, UInt32 tabIndex);
|
||||
|
||||
UInt64 SourceWindow { get; };
|
||||
UInt64 TargetWindow { get; };
|
||||
UInt32 TabIndex { get; };
|
||||
};
|
||||
|
||||
interface IDialogPresenter
|
||||
{
|
||||
@ -74,6 +81,7 @@ namespace TerminalApp
|
||||
|
||||
Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
|
||||
void WindowActivated(Boolean activated);
|
||||
void SendContentToOther(RequestReceiveContentArgs args);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
|
||||
@ -92,6 +100,6 @@ namespace TerminalApp
|
||||
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.ShowWindowArgs> ShowWindowChanged;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1182,6 +1182,13 @@ namespace winrt::TerminalApp::implementation
|
||||
_root->AttachContent(content, tabIndex);
|
||||
}
|
||||
}
|
||||
void TerminalWindow::SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args)
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
_root->SendContentToOther(args);
|
||||
}
|
||||
}
|
||||
|
||||
bool TerminalWindow::ShouldImmediatelyHandoffToElevated()
|
||||
{
|
||||
|
||||
@ -143,6 +143,7 @@ namespace winrt::TerminalApp::implementation
|
||||
TerminalApp::WindowProperties WindowProperties() { return *_WindowProperties; }
|
||||
|
||||
void AttachContent(winrt::hstring content, uint32_t tabIndex);
|
||||
void SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs args);
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// PropertyChanged is surprisingly not a typed event, so we'll define that one manually.
|
||||
@ -219,6 +220,7 @@ namespace winrt::TerminalApp::implementation
|
||||
TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs);
|
||||
|
||||
FORWARDED_TYPED_EVENT(RequestMoveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestMoveContentArgs, _root, RequestMoveContent);
|
||||
FORWARDED_TYPED_EVENT(RequestReceiveContent, Windows::Foundation::IInspectable, winrt::TerminalApp::RequestReceiveContentArgs, _root, RequestReceiveContent);
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class TerminalAppLocalTests::CommandlineTest;
|
||||
|
||||
@ -137,7 +137,9 @@ namespace TerminalApp
|
||||
event Windows.Foundation.TypedEventHandler<Object, SettingsLoadEventArgs> SettingsChanged;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, RequestMoveContentArgs> RequestMoveContent;
|
||||
void AttachContent(String content, UInt32 tabIndex);
|
||||
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;
|
||||
|
||||
void AttachContent(String content, UInt32 tabIndex);
|
||||
void SendContentToOther(RequestReceiveContentArgs args);
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,10 +72,10 @@ namespace Microsoft.Terminal.Control
|
||||
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> Attached;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> Closed;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> Attached;
|
||||
|
||||
// Used to communicate to the TermControl, but not necessarily higher up in the stack
|
||||
event Windows.Foundation.TypedEventHandler<Object, ContextMenuRequestedEventArgs> ContextMenuRequested;
|
||||
|
||||
|
||||
@ -86,6 +86,7 @@ namespace RemotingUnitTests
|
||||
void RequestQuitAll() DIE;
|
||||
void Quit() DIE;
|
||||
void AttachContentToWindow(Remoting::AttachRequest) DIE;
|
||||
void SendContent(winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs) DIE;
|
||||
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, Remoting::WindowActivatedArgs);
|
||||
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, Remoting::CommandlineArgs);
|
||||
TYPED_EVENT(IdentifyWindowsRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
@ -98,6 +99,7 @@ namespace RemotingUnitTests
|
||||
TYPED_EVENT(QuitRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, Remoting::GetWindowLayoutArgs);
|
||||
TYPED_EVENT(AttachRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::AttachRequest);
|
||||
TYPED_EVENT(SendContentRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs);
|
||||
};
|
||||
|
||||
// Same idea.
|
||||
@ -117,6 +119,7 @@ namespace RemotingUnitTests
|
||||
winrt::Windows::Foundation::Collections::IVectorView<Remoting::PeasantInfo> GetPeasantInfos() DIE;
|
||||
winrt::Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts() DIE;
|
||||
void RequestMoveContent(winrt::hstring, winrt::hstring, uint32_t) DIE;
|
||||
void RequestSendContent(Remoting::RequestReceiveContentArgs) DIE;
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, Remoting::FindTargetWindowArgs);
|
||||
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
@ -384,7 +384,8 @@ void AppHost::Initialize()
|
||||
_revokers.QuitRequested = _windowLogic.QuitRequested(winrt::auto_revoke, { this, &AppHost::_RequestQuitAll });
|
||||
_revokers.ShowWindowChanged = _windowLogic.ShowWindowChanged(winrt::auto_revoke, { this, &AppHost::_ShowWindowChanged });
|
||||
_revokers.RequestMoveContent = _windowLogic.RequestMoveContent(winrt::auto_revoke, { this, &AppHost::_handleMoveContent });
|
||||
|
||||
_revokers.RequestReceiveContent = _windowLogic.RequestReceiveContent(winrt::auto_revoke, { this, &AppHost::_handleReceiveContent });
|
||||
_revokers.SendContentRequested = _peasant.SendContentRequested(winrt::auto_revoke, { this, &AppHost::_handleSendContent });
|
||||
// BODGY
|
||||
// On certain builds of Windows, when Terminal is set as the default
|
||||
// it will accumulate an unbounded amount of queued animations while
|
||||
@ -1232,6 +1233,23 @@ void AppHost::_handleAttach(const winrt::Windows::Foundation::IInspectable& /*se
|
||||
_windowLogic.AttachContent(args.Content(), args.TabIndex());
|
||||
}
|
||||
|
||||
// Page -> us -> manager -> monarch
|
||||
// The page wants to tell the monarch that it was the drop target for a drag drop.
|
||||
// The manager will tell the monarch to tell the _other_ window to send its content to us.
|
||||
void AppHost::_handleReceiveContent(const winrt::Windows::Foundation::IInspectable& /* sender */,
|
||||
winrt::TerminalApp::RequestReceiveContentArgs args)
|
||||
{
|
||||
_windowManager.RequestSendContent(winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs{ args.SourceWindow(), args.TargetWindow(), args.TabIndex() });
|
||||
}
|
||||
|
||||
// monarch -> Peasant -> us -> Page
|
||||
// The Monarch was told to tell us to send our dragged content to someone else.
|
||||
void AppHost::_handleSendContent(const winrt::Windows::Foundation::IInspectable& /* sender */,
|
||||
winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs args)
|
||||
{
|
||||
_windowLogic.SendContentToOther(winrt::TerminalApp::RequestReceiveContentArgs{ args.SourceWindow(), args.TargetWindow(), args.TabIndex() });
|
||||
}
|
||||
|
||||
// Bubble the update settings request up to the emperor. We're being called on
|
||||
// the Window thread, but the Emperor needs to update the settings on the _main_
|
||||
// thread.
|
||||
|
||||
@ -127,6 +127,14 @@ private:
|
||||
|
||||
void _requestUpdateSettings();
|
||||
|
||||
// Page -> us -> monarch
|
||||
void _handleReceiveContent(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
winrt::TerminalApp::RequestReceiveContentArgs args);
|
||||
|
||||
// monarch -> us -> Page
|
||||
void _handleSendContent(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
winrt::Microsoft::Terminal::Remoting::RequestReceiveContentArgs args);
|
||||
|
||||
winrt::event_token _GetWindowLayoutRequestedToken;
|
||||
|
||||
// Helper struct. By putting these all into one struct, we can revoke them
|
||||
@ -161,9 +169,11 @@ private:
|
||||
winrt::TerminalApp::TerminalWindow::QuitRequested_revoker QuitRequested;
|
||||
winrt::TerminalApp::TerminalWindow::ShowWindowChanged_revoker ShowWindowChanged;
|
||||
winrt::TerminalApp::TerminalWindow::RequestMoveContent_revoker RequestMoveContent;
|
||||
winrt::TerminalApp::TerminalWindow::RequestReceiveContent_revoker RequestReceiveContent;
|
||||
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
|
||||
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested;
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant::SendContentRequested_revoker SendContentRequested;
|
||||
} _revokers{};
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user