Indicate which pane is focused with the Accent color on the pan… (#3060)

## Summary of the Pull Request

Adds a small border with the accent color to indicate a pane is focused

<img src="https://user-images.githubusercontent.com/18356694/66218711-560e4b80-e68f-11e9-85b0-1f387d35bb92.png" width="480">
<img src="https://user-images.githubusercontent.com/18356694/66218757-6f16fc80-e68f-11e9-8d39-db9ab748c4de.png" width="480">
<img src="https://user-images.githubusercontent.com/18356694/66219194-55c28000-e690-11e9-9835-8b5212e70e8a.png" width="480">

## PR Checklist
* [x] Closes #994
* [x] I work here
* [😢] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

I've removed the simple Grid we were using as the pane separator, and replaced it with a Border that might appear on any side of a pane.

When we add a split, we'll create each child with one of the `Border` flags set (each child with one of a pair of flags). E.g. creating a horizontal split creates one child with the `Top` border, and another with the `Bottom`. 

Then, if one of those panes is split, it will pass it's border flag to is new children, with the additional flag set. So adding another Vertical split to the above scenario would create a set of panes with either (`Top|Left`, `Top|Right`) or (`Bottom|Left`, `Bottom|Right`) borders set, depending on which pane was split.

<hr>

* start work on this by tracking border state

* Colorize the border

* Use the accent color for highlighting

* Cleanup the accent color code

* Don't buy any rural real estate when closing a pane

* Closing panes works well now too

* Cleanup for review

* Update src/cascadia/TerminalApp/Pane.cpp

* try some things that don't work to fix the resizing crash

* Revert "try some things that don't work to fix the resizing crash"

This reverts commit 3fc14da113.

* this _does_ work, but I think it's not semantically correct

* This doesn't seem to work either.

  I tried adding the pane seperators to the Pane::_GetMinWidth calculation. That
  works for prevent the crash, but the resizing is wonky now. If you add a
  Vertical split, then a second, then resize the middle pane really small,
  you'll see that the _last_ resize doesn't work properly. The text seems to
  overhand into the border.

  Additionally, there's really weird behavior resizing panes to be small. They
  don't always seem to be resizable to the smallest size.

* Revert "This doesn't seem to work either."

This reverts commit 2fd8323e7b.

* Merge the changes from the "this is the one" branch

  Again, no idea what I really did that worked, but it does

* Cleanup from my mess of a commit

  This makes so much more sense now

* Other PR feedback from @carlos-zamora

* Fix a typo
This commit is contained in:
Mike Griese 2019-11-01 15:06:11 -05:00 committed by GitHub
parent 86e0ea73e2
commit 64943bd033
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 164 additions and 55 deletions

View file

@ -5,21 +5,28 @@
#include "Pane.h"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::Xaml::Media;
using namespace winrt::Microsoft::Terminal::Settings;
using namespace winrt::Microsoft::Terminal::TerminalControl;
using namespace winrt::TerminalApp;
static const int PaneSeparatorSize = 4;
static const int PaneBorderSize = 2;
static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
static const float Half = 0.50f;
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr };
Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocused) :
_control{ control },
_lastFocused{ lastFocused },
_profile{ profile }
{
_root.Children().Append(_control);
_root.Children().Append(_border);
_border.Child(_control);
_connectionClosedToken = _control.ConnectionClosed({ this, &Pane::_ControlClosedHandler });
// Set the background of the pane to match that of the theme's default grid
@ -37,6 +44,24 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus
_root.Style(style);
}
}
if (s_focusedBorderBrush == nullptr)
{
const auto accentColorKey = winrt::box_value(L"SystemAccentColor");
if (res.HasKey(accentColorKey))
{
const auto colorFromResources = res.Lookup(accentColorKey);
// If SystemAccentColor is _not_ a Color for some reason, use
// Transparent as the color, so we don't do this process again on
// the next pane (by leaving s_focusedBorderBrush nullptr)
auto actualColor = winrt::unbox_value_or<Color>(colorFromResources, Colors::Transparent());
s_focusedBorderBrush = SolidColorBrush(actualColor);
}
else
{
s_focusedBorderBrush = SolidColorBrush{ Colors::Transparent() };
}
}
}
// Method Description:
@ -108,14 +133,13 @@ bool Pane::_Resize(const Direction& direction)
// actualDimension is the size in DIPs of this pane in the direction we're
// resizing.
auto actualDimension = changeWidth ? actualSize.Width : actualSize.Height;
actualDimension -= PaneSeparatorSize;
const auto firstMinSize = _firstChild->_GetMinSize();
const auto secondMinSize = _secondChild->_GetMinSize();
// These are the minimum amount of space we need for each of our children
const auto firstMinDimension = changeWidth ? firstMinSize.Width : firstMinSize.Height;
const auto secondMinDimension = changeWidth ? secondMinSize.Width : secondMinSize.Height;
const auto firstMinDimension = (changeWidth ? firstMinSize.Width : firstMinSize.Height) + PaneBorderSize;
const auto secondMinDimension = (changeWidth ? secondMinSize.Width : secondMinSize.Height) + PaneBorderSize;
const auto firstMinPercent = firstMinDimension / actualDimension;
const auto secondMinPercent = secondMinDimension / actualDimension;
@ -124,6 +148,11 @@ bool Pane::_Resize(const Direction& direction)
// to reserve for the second.
const auto firstMaxPercent = 1.0f - secondMinPercent;
if (firstMaxPercent < firstMinPercent)
{
return false;
}
_firstPercent = std::clamp(_firstPercent.value() - amount, firstMinPercent, firstMaxPercent);
// Update the other child to fill the remaining percent
_secondPercent = 1.0f - _firstPercent.value();
@ -446,10 +475,13 @@ void Pane::UpdateFocus()
_control.FocusState() != FocusState::Unfocused;
_lastFocused = controlFocused;
_border.BorderBrush(_lastFocused ? s_focusedBorderBrush : nullptr);
}
else
{
_lastFocused = false;
_firstChild->UpdateFocus();
_secondChild->UpdateFocus();
}
@ -530,6 +562,13 @@ void Pane::_CloseChild(const bool closeFirst)
// If the only child left is a leaf, that means we're a leaf now.
if (remainingChild->_IsLeaf())
{
// When the remaining child is a leaf, that means both our children were
// previously leaves, and the only difference in their borders is the
// border that we gave them. Take a bitwise AND of those two children to
// remove that border. Other borders the children might have, they
// inherited from us, so the flag will be set for both children.
_borders = _firstChild->_borders & _secondChild->_borders;
// take the control and profile of the pane that _wasn't_ closed.
_control = remainingChild->_control;
_profile = remainingChild->_profile;
@ -554,15 +593,18 @@ void Pane::_CloseChild(const bool closeFirst)
// re-attach the TermControl to our Grid.
_firstChild->_root.Children().Clear();
_secondChild->_root.Children().Clear();
_firstChild->_border.Child(nullptr);
_secondChild->_border.Child(nullptr);
// Reset our UI:
_root.Children().Clear();
_border.Child(nullptr);
_root.ColumnDefinitions().Clear();
_root.RowDefinitions().Clear();
_separatorRoot = { nullptr };
// Reattach the TermControl to our grid.
_root.Children().Append(_control);
_root.Children().Append(_border);
_border.Child(_control);
if (_lastFocused)
{
@ -571,12 +613,26 @@ void Pane::_CloseChild(const bool closeFirst)
_splitState = SplitState::None;
_UpdateBorders();
// Release our children.
_firstChild = nullptr;
_secondChild = nullptr;
}
else
{
// Determine which border flag we gave to the child when we first split
// it, so that we can take just that flag away from them.
Borders clearBorderFlag = Borders::None;
if (_splitState == SplitState::Horizontal)
{
clearBorderFlag = closeFirst ? Borders::Top : Borders::Bottom;
}
else if (_splitState == SplitState::Vertical)
{
clearBorderFlag = closeFirst ? Borders::Left : Borders::Right;
}
// First stash away references to the old panes and their tokens
const auto oldFirstToken = _firstClosedToken;
const auto oldSecondToken = _secondClosedToken;
@ -585,7 +641,6 @@ void Pane::_CloseChild(const bool closeFirst)
// Steal all the state from our child
_splitState = remainingChild->_splitState;
_separatorRoot = remainingChild->_separatorRoot;
_firstChild = remainingChild->_firstChild;
_secondChild = remainingChild->_secondChild;
@ -603,6 +658,7 @@ void Pane::_CloseChild(const bool closeFirst)
// Reset our UI:
_root.Children().Clear();
_border.Child(nullptr);
_root.ColumnDefinitions().Clear();
_root.RowDefinitions().Clear();
@ -626,11 +682,19 @@ void Pane::_CloseChild(const bool closeFirst)
// Remove the child's UI elements from the child's grid, so we can
// attach them to us instead.
remainingChild->_root.Children().Clear();
remainingChild->_border.Child(nullptr);
_root.Children().Append(_firstChild->GetRootElement());
_root.Children().Append(_separatorRoot);
_root.Children().Append(_secondChild->GetRootElement());
// Take the flag away from the children that they inherited from their
// parent, and update their borders to visually match
WI_ClearAllFlags(_firstChild->_borders, clearBorderFlag);
WI_ClearAllFlags(_secondChild->_borders, clearBorderFlag);
_UpdateBorders();
_firstChild->_UpdateBorders();
_secondChild->_UpdateBorders();
// If the closed child was focused, transfer the focus to it's first sibling.
if (closedChild->_lastFocused)
{
@ -640,7 +704,6 @@ void Pane::_CloseChild(const bool closeFirst)
// Release the pointers that the child was holding.
remainingChild->_firstChild = nullptr;
remainingChild->_secondChild = nullptr;
remainingChild->_separatorRoot = { nullptr };
}
}
@ -682,10 +745,7 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
{
_root.ColumnDefinitions().Clear();
// Create three columns in this grid: one for each pane, and one for the separator.
auto separatorColDef = Controls::ColumnDefinition();
separatorColDef.Width(GridLengthHelper::Auto());
// Create two columns in this grid: one for each pane
const auto paneSizes = _GetPaneSizes(rootSize.Width);
auto firstColDef = Controls::ColumnDefinition();
@ -695,17 +755,13 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second));
_root.ColumnDefinitions().Append(firstColDef);
_root.ColumnDefinitions().Append(separatorColDef);
_root.ColumnDefinitions().Append(secondColDef);
}
else if (_splitState == SplitState::Horizontal)
{
_root.RowDefinitions().Clear();
// Create three rows in this grid: one for each pane, and one for the separator.
auto separatorRowDef = Controls::RowDefinition();
separatorRowDef.Height(GridLengthHelper::Auto());
// Create two rows in this grid: one for each pane
const auto paneSizes = _GetPaneSizes(rootSize.Height);
auto firstRowDef = Controls::RowDefinition();
@ -715,7 +771,6 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second));
_root.RowDefinitions().Append(firstRowDef);
_root.RowDefinitions().Append(separatorRowDef);
_root.RowDefinitions().Append(secondRowDef);
}
}
@ -734,23 +789,36 @@ void Pane::_CreateSplitContent()
gsl::narrow_cast<float>(_root.ActualHeight()) };
_CreateRowColDefinitions(actualSize);
}
if (_splitState == SplitState::Vertical)
// Method Description:
// - Sets the thickness of each side of our borders to match our _borders state.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Pane::_UpdateBorders()
{
double top = 0, bottom = 0, left = 0, right = 0;
Thickness newBorders{ 0 };
if (WI_IsFlagSet(_borders, Borders::Top))
{
// Create the pane separator
_separatorRoot = Controls::Grid{};
_separatorRoot.Width(PaneSeparatorSize);
// NaN is the special value XAML uses for "Auto" sizing.
_separatorRoot.Height(NAN);
top = PaneBorderSize;
}
else if (_splitState == SplitState::Horizontal)
if (WI_IsFlagSet(_borders, Borders::Bottom))
{
// Create the pane separator
_separatorRoot = Controls::Grid{};
_separatorRoot.Height(PaneSeparatorSize);
// NaN is the special value XAML uses for "Auto" sizing.
_separatorRoot.Width(NAN);
bottom = PaneBorderSize;
}
if (WI_IsFlagSet(_borders, Borders::Left))
{
left = PaneBorderSize;
}
if (WI_IsFlagSet(_borders, Borders::Right))
{
right = PaneBorderSize;
}
_border.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom));
}
// Method Description:
@ -764,14 +832,28 @@ void Pane::_ApplySplitDefinitions()
if (_splitState == SplitState::Vertical)
{
Controls::Grid::SetColumn(_firstChild->GetRootElement(), 0);
Controls::Grid::SetColumn(_separatorRoot, 1);
Controls::Grid::SetColumn(_secondChild->GetRootElement(), 2);
Controls::Grid::SetColumn(_secondChild->GetRootElement(), 1);
_firstChild->_borders = _borders | Borders::Right;
_secondChild->_borders = _borders | Borders::Left;
_borders = Borders::None;
_UpdateBorders();
_firstChild->_UpdateBorders();
_secondChild->_UpdateBorders();
}
else if (_splitState == SplitState::Horizontal)
{
Controls::Grid::SetRow(_firstChild->GetRootElement(), 0);
Controls::Grid::SetRow(_separatorRoot, 1);
Controls::Grid::SetRow(_secondChild->GetRootElement(), 2);
Controls::Grid::SetRow(_secondChild->GetRootElement(), 1);
_firstChild->_borders = _borders | Borders::Bottom;
_secondChild->_borders = _borders | Borders::Top;
_borders = Borders::None;
_UpdateBorders();
_firstChild->_UpdateBorders();
_secondChild->_UpdateBorders();
}
}
@ -845,7 +927,7 @@ bool Pane::_CanSplit(SplitState splitType)
if (splitType == SplitState::Vertical)
{
const auto widthMinusSeparator = actualSize.Width - PaneSeparatorSize;
const auto widthMinusSeparator = actualSize.Width - CombinedPaneBorderSize;
const auto newWidth = widthMinusSeparator * Half;
return newWidth > minSize.Width;
@ -853,7 +935,7 @@ bool Pane::_CanSplit(SplitState splitType)
if (splitType == SplitState::Horizontal)
{
const auto heightMinusSeparator = actualSize.Height - PaneSeparatorSize;
const auto heightMinusSeparator = actualSize.Height - CombinedPaneBorderSize;
const auto newHeight = heightMinusSeparator * Half;
return newHeight > minSize.Height;
@ -891,6 +973,7 @@ void Pane::_Split(SplitState splitType, const GUID& profile, const TermControl&
// Remove any children we currently have. We can't add the existing
// TermControl to a new grid until we do this.
_root.Children().Clear();
_border.Child(nullptr);
// Create two new Panes
// Move our control, guid into the first one.
@ -901,7 +984,6 @@ void Pane::_Split(SplitState splitType, const GUID& profile, const TermControl&
_secondChild = std::make_shared<Pane>(profile, control);
_root.Children().Append(_firstChild->GetRootElement());
_root.Children().Append(_separatorRoot);
_root.Children().Append(_secondChild->GetRootElement());
_ApplySplitDefinitions();
@ -914,11 +996,11 @@ void Pane::_Split(SplitState splitType, const GUID& profile, const TermControl&
// Method Description:
// - Gets the size in pixels of each of our children, given the full size they
// should fill. Accounts for the size of the separator that should be between
// them as well.
// should fill. Since these children own their own separators (borders), this
// size is their portion of our _entire_ size.
// Arguments:
// - fullSize: the amount of space in pixels that should be filled by our
// children and their separator
// children and their separators
// Return Value:
// - a pair with the size of our first child and the size of our second child,
// respectively.
@ -929,16 +1011,16 @@ std::pair<float, float> Pane::_GetPaneSizes(const float& fullSize)
THROW_HR(E_FAIL);
}
const auto sizeMinusSeparator = fullSize - PaneSeparatorSize;
const auto firstSize = sizeMinusSeparator * _firstPercent.value();
const auto secondSize = sizeMinusSeparator * _secondPercent.value();
const auto firstSize = fullSize * _firstPercent.value();
const auto secondSize = fullSize * _secondPercent.value();
return { firstSize, secondSize };
}
// Method Description:
// - Get the absolute minimum size that this pane can be resized to and still
// have 1x1 character visible, in each of its children. This includes the
// space needed for the separator.
// have 1x1 character visible, in each of its children. If we're a leaf, we'll
// include the space needed for borders _within_ us.
// Arguments:
// - <none>
// Return Value:
@ -948,14 +1030,27 @@ Size Pane::_GetMinSize() const
{
if (_IsLeaf())
{
return _control.MinimumSize();
}
auto controlSize = _control.MinimumSize();
auto newWidth = controlSize.Width;
auto newHeight = controlSize.Height;
const auto firstSize = _firstChild->_GetMinSize();
const auto secondSize = _secondChild->_GetMinSize();
const auto newWidth = firstSize.Width + secondSize.Width + (_splitState == SplitState::Vertical ? PaneSeparatorSize : 0);
const auto newHeight = firstSize.Height + secondSize.Height + (_splitState == SplitState::Horizontal ? PaneSeparatorSize : 0);
return { newWidth, newHeight };
newWidth += WI_IsFlagSet(_borders, Borders::Left) ? CombinedPaneBorderSize : 0;
newWidth += WI_IsFlagSet(_borders, Borders::Right) ? CombinedPaneBorderSize : 0;
newHeight += WI_IsFlagSet(_borders, Borders::Top) ? CombinedPaneBorderSize : 0;
newHeight += WI_IsFlagSet(_borders, Borders::Bottom) ? CombinedPaneBorderSize : 0;
return { newWidth, newHeight };
}
else
{
const auto firstSize = _firstChild->_GetMinSize();
const auto secondSize = _secondChild->_GetMinSize();
const auto newWidth = firstSize.Width + secondSize.Width;
const auto newHeight = firstSize.Height + secondSize.Height;
return { newWidth, newHeight };
}
}
DEFINE_EVENT(Pane, Closed, _closedHandlers, ConnectionClosedEventArgs);

View file

@ -23,6 +23,16 @@
#include <winrt/TerminalApp.h>
#include "../../cascadia/inc/cppwinrt_utils.h"
enum class Borders : int
{
None = 0x0,
Top = 0x1,
Bottom = 0x2,
Left = 0x4,
Right = 0x8
};
DEFINE_ENUM_FLAG_OPERATORS(Borders);
class Pane : public std::enable_shared_from_this<Pane>
{
public:
@ -58,8 +68,9 @@ public:
private:
winrt::Windows::UI::Xaml::Controls::Grid _root{};
winrt::Windows::UI::Xaml::Controls::Grid _separatorRoot{ nullptr };
winrt::Windows::UI::Xaml::Controls::Border _border{};
winrt::Microsoft::Terminal::TerminalControl::TermControl _control{ nullptr };
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush;
std::shared_ptr<Pane> _firstChild{ nullptr };
std::shared_ptr<Pane> _secondChild{ nullptr };
@ -75,6 +86,8 @@ private:
std::shared_mutex _createCloseLock{};
Borders _borders{ Borders::None };
bool _IsLeaf() const noexcept;
bool _HasFocusedChild() const noexcept;
void _SetupChildCloseHandlers();
@ -84,6 +97,7 @@ private:
void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize);
void _CreateSplitContent();
void _ApplySplitDefinitions();
void _UpdateBorders();
bool _Resize(const winrt::TerminalApp::Direction& direction);
bool _NavigateFocus(const winrt::TerminalApp::Direction& direction);