Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an animation to pane entrance/exit #7364

Merged
23 commits merged into from
Oct 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ebf75ed
Move the dll project to /dll and move the lib project up
zadjii-msft Aug 20, 2020
cb468e6
Huh, this definitely creates an animation, but it's got HORRIFYING pe…
zadjii-msft Aug 20, 2020
22a8b4d
animating one split > animating 0 splits
zadjii-msft Aug 20, 2020
d7e872c
hot damn this is so C R I S P
zadjii-msft Aug 20, 2020
d472fe5
Add a bunch of code cleanup
zadjii-msft Aug 20, 2020
dcbba94
Try animating both the first and second panes
zadjii-msft Aug 21, 2020
a09ec80
Pane.cpp is ready for review, just gotta add the setting now
zadjii-msft Aug 21, 2020
522ab15
add a setting to disable animations, ready for review
zadjii-msft Aug 21, 2020
f48fbfe
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes-…
zadjii-msft Aug 21, 2020
188c969
okay bot
zadjii-msft Aug 21, 2020
facc672
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes-…
zadjii-msft Aug 26, 2020
fde5dca
holy shit it finally worked
zadjii-msft Aug 27, 2020
38ddc35
Update this to look more crisp
zadjii-msft Aug 27, 2020
ae04952
Clean up this code for review
zadjii-msft Aug 27, 2020
bcb675f
gottem
zadjii-msft Aug 27, 2020
3c7d53f
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes-…
zadjii-msft Aug 28, 2020
cddf16c
disable this animation when animations are disabled in the OS
zadjii-msft Aug 28, 2020
994d7e0
spellcheck nits
zadjii-msft Sep 29, 2020
984a309
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes-…
zadjii-msft Sep 29, 2020
d95e5c4
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes-…
zadjii-msft Oct 5, 2020
1653fa3
Some merge conflicts fromt the month I was awaty
zadjii-msft Oct 5, 2020
5f09776
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes-…
zadjii-msft Oct 7, 2020
082be75
do something to make the azp run
DHowett Oct 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,11 @@
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
"$ref": "#/definitions/CopyFormat"
},
"disableAnimations": {
"default": false,
"description": "When set to `true`, visual animations will be disabled across the application.",
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
"type": "boolean"
},
"largePasteWarning": {
"default": true,
"description": "When set to true, trying to paste text with more than 5 KiB of characters will display a warning asking you whether to continue or not with the paste.",
Expand Down
275 changes: 273 additions & 2 deletions src/cascadia/TerminalApp/Pane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ static const int PaneBorderSize = 2;
static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
static const float Half = 0.50f;

// WARNING: Don't do this! This won't work
// Duration duration{ std::chrono::milliseconds{ 200 } };
// Instead, make a duration from a TimeSpan from the time in millis
//
// 200ms was chosen because it's quick enough that it doesn't break your
// flow, but not too quick to see
static const int AnimationDurationInMilliseconds = 200;
static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the annoying bit here is that this has to be constructed during llibrary initialization :(


winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr };
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_unfocusedBorderBrush = { nullptr };

Expand All @@ -42,6 +51,10 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus
_SetupResources();
}

// Use the unfocused border color as the pane background, so an actual color
// appears behind panes as we animate them sliding in.
_root.Background(s_unfocusedBorderBrush);

// Register an event with the control to have it inform us when it gains focus.
_gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });

Expand Down Expand Up @@ -327,7 +340,7 @@ void Pane::_ControlConnectionStateChangedHandler(const TermControl& /*sender*/,
if ((mode == CloseOnExitMode::Always) ||
(mode == CloseOnExitMode::Graceful && newConnectionState == ConnectionState::Closed))
{
_ClosedHandlers(nullptr, nullptr);
Close();
}
}
}
Expand Down Expand Up @@ -761,7 +774,129 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)

if (auto pane{ weakThis.get() })
{
_CloseChild(closeFirst);
// This will query if animations are enabled via the "Show animations in
// Windows" setting in the OS
winrt::Windows::UI::ViewManagement::UISettings uiSettings;
const auto animationsEnabledInOS = uiSettings.AnimationsEnabled();
const auto animationsEnabledInApp = Media::Animation::Timeline::AllowDependentAnimations();

// If animations are disabled, just skip this and go straight to
// _CloseChild. Curiously, the pane opening animation doesn't need this,
// and will skip straight to Completed when animations are disabled, but
// this one doesn't seem to.
if (!animationsEnabledInOS || !animationsEnabledInApp)
{
pane->_CloseChild(closeFirst);
co_return;
}

// Setup the animation

auto removedChild = closeFirst ? _firstChild : _secondChild;
auto remainingChild = closeFirst ? _secondChild : _firstChild;
const bool splitWidth = _splitState == SplitState::Vertical;
const auto totalSize = splitWidth ? _root.ActualWidth() : _root.ActualHeight();

Size removedOriginalSize{
::base::saturated_cast<float>(removedChild->_root.ActualWidth()),
::base::saturated_cast<float>(removedChild->_root.ActualHeight())
};
Size remainingOriginalSize{
::base::saturated_cast<float>(remainingChild->_root.ActualWidth()),
::base::saturated_cast<float>(remainingChild->_root.ActualHeight())
};

// Remove both children from the grid
_root.Children().Clear();
// Add the remaining child back to the grid, in the right place.
_root.Children().Append(remainingChild->GetRootElement());
if (_splitState == SplitState::Vertical)
{
Controls::Grid::SetColumn(remainingChild->GetRootElement(), closeFirst ? 1 : 0);
}
else if (_splitState == SplitState::Horizontal)
{
Controls::Grid::SetRow(remainingChild->GetRootElement(), closeFirst ? 1 : 0);
}

// Create the dummy grid. This grid will be the one we actually animate,
// in the place of the closed pane.
Controls::Grid dummyGrid;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be a border? we'll never use its containment properties..

idk, nit

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Still curious tho)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just went with a Grid because that's my mental go-to "I need a 2d space" control. Is there a real difference the way I'm using this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naw

dummyGrid.Background(s_unfocusedBorderBrush);
// It should be the size of the closed pane.
dummyGrid.Width(removedOriginalSize.Width);
dummyGrid.Height(removedOriginalSize.Height);
// Put it where the removed child is
if (_splitState == SplitState::Vertical)
{
Controls::Grid::SetColumn(dummyGrid, closeFirst ? 0 : 1);
}
else if (_splitState == SplitState::Horizontal)
{
Controls::Grid::SetRow(dummyGrid, closeFirst ? 0 : 1);
}
// Add it to the tree
_root.Children().Append(dummyGrid);

// Set up the rows/cols as auto/auto, so they'll only use the size of
// the elements in the grid.
//
// * For the closed pane, we want to make that row/col "auto" sized, so
// it takes up as much space as is available.
// * For the remaining pane, we'll make that row/col "*" sized, so it
// takes all the remaining space. As the dummy grid is resized down,
// the remaining pane will expand to take the rest of the space.
_root.ColumnDefinitions().Clear();
_root.RowDefinitions().Clear();
if (_splitState == SplitState::Vertical)
{
auto firstColDef = Controls::ColumnDefinition();
auto secondColDef = Controls::ColumnDefinition();
firstColDef.Width(!closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto());
secondColDef.Width(closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto());
_root.ColumnDefinitions().Append(firstColDef);
_root.ColumnDefinitions().Append(secondColDef);
}
else if (_splitState == SplitState::Horizontal)
{
auto firstRowDef = Controls::RowDefinition();
auto secondRowDef = Controls::RowDefinition();
firstRowDef.Height(!closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto());
secondRowDef.Height(closeFirst ? GridLengthHelper::FromValueAndType(1, GridUnitType::Star) : GridLengthHelper::Auto());
_root.RowDefinitions().Append(firstRowDef);
_root.RowDefinitions().Append(secondRowDef);
}

// Animate the dummy grid from its current size down to 0
Media::Animation::DoubleAnimation animation{};
animation.Duration(AnimationDuration);
animation.From(splitWidth ? removedOriginalSize.Width : removedOriginalSize.Height);
animation.To(0.0);
// This easing is the same as the entrance animation.
animation.EasingFunction(Media::Animation::QuadraticEase{});
animation.EnableDependentAnimation(true);

Media::Animation::Storyboard s;
s.Duration(AnimationDuration);
s.Children().Append(animation);
s.SetTarget(animation, dummyGrid);
s.SetTargetProperty(animation, splitWidth ? L"Width" : L"Height");

// Start the animation.
s.Begin();

std::weak_ptr<Pane> weakThis{ shared_from_this() };

// When the animation is completed, reparent the child's content up to
// us, and remove the child nodes from the tree.
animation.Completed([weakThis, closeFirst](auto&&, auto&&) {
if (auto pane{ weakThis.lock() })
{
// We don't need to manually undo any of the above trickiness.
// We're going to re-parent the child's content into us anyways
pane->_CloseChild(closeFirst);
}
});
}
}

Expand Down Expand Up @@ -903,6 +1038,140 @@ void Pane::_ApplySplitDefinitions()
}
}

// Method Description:
// - Create a pair of animations when a new control enters this pane. This
// should _ONLY_ be called in _Split, AFTER the first and second child panes
// have been set up.
void Pane::_SetupEntranceAnimation()
{
// This will query if animations are enabled via the "Show animations in
// Windows" setting in the OS
winrt::Windows::UI::ViewManagement::UISettings uiSettings;
const auto animationsEnabledInOS = uiSettings.AnimationsEnabled();

const bool splitWidth = _splitState == SplitState::Vertical;
const auto totalSize = splitWidth ? _root.ActualWidth() : _root.ActualHeight();
// If we don't have a size yet, it's likely that we're in startup, or we're
// being executed as a sequence of actions. In that case, just skip the
// animation.
if (totalSize <= 0 || !animationsEnabledInOS)
{
return;
}

const auto [firstSize, secondSize] = _CalcChildrenSizes(::base::saturated_cast<float>(totalSize));

// This is safe to capture this, because it's only being called in the
// context of this method (not on another thread)
auto setupAnimation = [&](const auto& size, const bool isFirstChild) {
auto child = isFirstChild ? _firstChild : _secondChild;
auto childGrid = child->_root;
auto control = child->_control;
// Build up our animation:
// * it'll take as long as our duration (200ms)
// * it'll change the value of our property from 0 to secondSize
// * it'll animate that value using a quadratic function (like f(t) = t^2)
// * IMPORTANT! We'll manually tell the animation that "yes we know what
// we're doing, we want an animation here."
Media::Animation::DoubleAnimation animation{};
animation.Duration(AnimationDuration);
if (isFirstChild)
{
// If we're animating the first pane, the size should decrease, from
// the full size down to the given size.
animation.From(totalSize);
animation.To(size);
}
else
{
// Otherwise, we want to show the pane getting larger, so animate
// from 0 to the requested size.
animation.From(0.0);
animation.To(size);
}
animation.EasingFunction(Media::Animation::QuadraticEase{});
animation.EnableDependentAnimation(true);

// Now we're going to set up the Storyboard. This is a unit that uses the
// Animation from above, and actually applies it to a property.
// * we'll set it up for the same duration as the animation we have
// * Apply the animation to the grid of the new pane we're adding to the tree.
// * apply the animation to the Width or Height property.
Media::Animation::Storyboard s;
s.Duration(AnimationDuration);
s.Children().Append(animation);
s.SetTarget(animation, childGrid);
s.SetTargetProperty(animation, splitWidth ? L"Width" : L"Height");

// BE TRICKY:
// We're animating the width or height of our child pane's grid.
//
// We DON'T want to change the size of the control itself, because the
// terminal has to reflow the buffer every time the control changes size. So
// what we're going to do there is manually set the control's size to how
// big we _actually know_ the control will be.
//
// We're also going to be changing alignment of our child pane and the
// control. This way, we'll be able to have the control stick to the inside
// of the child pane's grid (the side that's moving), while we also have the
// pane's grid stick to "outside" of the grid (the side that's not moving)
if (splitWidth)
{
// If we're animating the first child, then stick to the top/left of
// the parent pane, otherwise use the bottom/right. This is always
// the "outside" of the parent pane.
childGrid.HorizontalAlignment(isFirstChild ? HorizontalAlignment::Left : HorizontalAlignment::Right);
control.HorizontalAlignment(HorizontalAlignment::Left);
control.Width(isFirstChild ? totalSize : size);
}
else
{
// If we're animating the first child, then stick to the top/left of
// the parent pane, otherwise use the bottom/right. This is always
// the "outside" of the parent pane.
childGrid.VerticalAlignment(isFirstChild ? VerticalAlignment::Top : VerticalAlignment::Bottom);
control.VerticalAlignment(VerticalAlignment::Top);
control.Height(isFirstChild ? totalSize : size);
}

// Start the animation.
s.Begin();

std::weak_ptr<Pane> weakThis{ shared_from_this() };
// When the animation is completed, undo the trickiness from before, to
// restore the controls to the behavior they'd usually have.
animation.Completed([weakThis, isFirstChild, splitWidth](auto&&, auto&&) {
if (auto pane{ weakThis.lock() })
{
auto child = isFirstChild ? pane->_firstChild : pane->_secondChild;
auto childGrid = child->_root;
if (auto control = child->_control)
{
if (splitWidth)
{
control.Width(NAN);
childGrid.Width(NAN);
childGrid.HorizontalAlignment(HorizontalAlignment::Stretch);
control.HorizontalAlignment(HorizontalAlignment::Stretch);
}
else
{
control.Height(NAN);
childGrid.Height(NAN);
childGrid.VerticalAlignment(VerticalAlignment::Stretch);
control.VerticalAlignment(VerticalAlignment::Stretch);
}
}
}
});
};

// TODO: GH#7365 - animating the first child right now doesn't _really_ do
// anything. We could do better though.
setupAnimation(firstSize, true);
setupAnimation(secondSize, false);
}

// Method Description:
// - Determines whether the pane can be split
// Arguments:
Expand Down Expand Up @@ -1177,6 +1446,8 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState

_lastActive = false;

_SetupEntranceAnimation();

return { _firstChild, _secondChild };
}

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/Pane.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class Pane : public std::enable_shared_from_this<Pane>

void _CreateRowColDefinitions();
void _ApplySplitDefinitions();
void _SetupEntranceAnimation();
void _UpdateBorders();

bool _Resize(const winrt::Microsoft::Terminal::Settings::Model::Direction& direction);
Expand Down
10 changes: 10 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ namespace winrt::TerminalApp::implementation
}
});

// Settings AllowDependentAnimations will affect whether animations are
// enabled application-wide, so we don't need to check it each time we
// want to create an animation.
WUX::Media::Animation::Timeline::AllowDependentAnimations(!_settings.GlobalSettings().DisableAnimations());

// Once the page is actually laid out on the screen, trigger all our
// startup actions. Things like Panes need to know at least how big the
// window will be, so they can subdivide that space.
Expand Down Expand Up @@ -2162,6 +2167,11 @@ namespace winrt::TerminalApp::implementation
// the alwaysOnTop setting will be lost.
_isAlwaysOnTop = _settings.GlobalSettings().AlwaysOnTop();
_alwaysOnTopChangedHandlers(*this, nullptr);

// Settings AllowDependentAnimations will affect whether animations are
// enabled application-wide, so we don't need to check it each time we
// want to create an animation.
WUX::Media::Animation::Timeline::AllowDependentAnimations(!_settings.GlobalSettings().DisableAnimations());
}

// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.UI.Xaml.Data.h>
#include <winrt/Windows.ui.xaml.media.h>
#include <winrt/Windows.UI.Xaml.Media.Animation.h>
#include <winrt/Windows.ui.xaml.input.h>
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.Xaml.Documents.h"
#include "winrt/Windows.UI.Xaml.Automation.h"
#include "winrt/Windows.UI.ViewManagement.h"
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>

Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" };
static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" };
static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" };
static constexpr std::string_view UseTabSwitcherKey{ "useTabSwitcher" };
static constexpr std::string_view DisableAnimationsKey{ "disableAnimations" };

static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };

Expand Down Expand Up @@ -153,6 +154,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)

JsonUtils::GetValueForKey(json, UseTabSwitcherKey, _UseTabSwitcher);

JsonUtils::GetValueForKey(json, DisableAnimationsKey, _DisableAnimations);

// This is a helper lambda to get the keybindings and commands out of both
// and array of objects. We'll use this twice, once on the legacy
// `keybindings` key, and again on the newer `bindings` key.
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/GlobalAppSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
GETSET_PROPERTY(bool, StartOnUserLogin, false);
GETSET_PROPERTY(bool, AlwaysOnTop, false);
GETSET_PROPERTY(bool, UseTabSwitcher, true);
GETSET_PROPERTY(bool, DisableAnimations, false);

private:
hstring _unparsedDefaultProfile;
Expand Down
Loading