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

Snap to character grid when resizing window #3181

Merged
merged 29 commits into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b8a5725
Kinda works, no splitted panes
mcpiroman Sep 24, 2019
68f613b
Calc sizes more accurately
mcpiroman Sep 26, 2019
5d0f1d3
General work, add resolution for children along resize axis
mcpiroman Sep 28, 2019
90649b2
Partially fix child displacement
mcpiroman Sep 29, 2019
cbda911
Change child layout algorithm, now 100% correct
mcpiroman Oct 5, 2019
d69f8ed
Cache padding and scrollbar width, fix infinite loop
mcpiroman Oct 5, 2019
b732e74
(drastically) optimize layout algorithm ( O(n^m) -> O(n) )
mcpiroman Oct 10, 2019
0ecbec5
Fix minimum pane size calculation
mcpiroman Oct 11, 2019
3dcd00f
Style, comments, format; fix layout just after split
mcpiroman Oct 12, 2019
583541d
Review changes
mcpiroman Oct 16, 2019
b00fef8
Fix rebase
mcpiroman Oct 18, 2019
5e9b31c
Undo caching of padding and scrollbar size
mcpiroman Nov 5, 2019
bed02c5
Some review changes
mcpiroman Nov 6, 2019
76d4945
Merge with master
mcpiroman Nov 7, 2019
9a95e18
Fix height calculation in non client window
mcpiroman Nov 11, 2019
11fcd51
Some additional comments I had time for
mcpiroman Nov 25, 2019
8034bb4
Finish comment in pane.h
mcpiroman Nov 25, 2019
14cc2d8
Merge with master 2
mcpiroman Nov 27, 2019
eb67b82
Things that should've gone with merge
mcpiroman Nov 27, 2019
b4febc8
Cleanup & format
mcpiroman Nov 28, 2019
cae3963
More comments
mcpiroman Nov 28, 2019
63ed26a
Review refactor, partly fix border size calculations
mcpiroman Dec 5, 2019
b0097d2
Small PR changes
mcpiroman Dec 11, 2019
cb0186a
Move Pane.LayoutSizeNode.cpp out of lib folder
mcpiroman Dec 11, 2019
bada548
Merge with master 3
mcpiroman Dec 11, 2019
24a88a5
Merge branch 'master' into 2834-snap-to-char-grid
mcpiroman Dec 18, 2019
8cf35e8
Merge branch 'master' into 2834-snap-to-char-grid
mcpiroman Dec 20, 2019
b933528
Github apparently switched files to LF, so revert that
mcpiroman Dec 20, 2019
1ebddfc
Some things that miniksa mentioned in #4068
mcpiroman Jan 4, 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
7 changes: 7 additions & 0 deletions src/cascadia/TerminalApp/AppLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,13 @@ namespace winrt::TerminalApp::implementation
return _settings->GlobalSettings().GetShowTabsInTitlebar();
}

// Method Description:
// - See Pane::CalcSnappedDimension
float AppLogic::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
{
return _root->CalcSnappedDimension(widthOrHeight, dimension);
}

// Method Description:
// - Attempt to load the settings. If we fail for any reason, returns an error.
// Return Value:
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
LaunchMode GetLaunchMode();
bool GetShowTabsInTitlebar();
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;

Windows::UI::Xaml::UIElement GetRoot() noexcept;

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.idl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ namespace TerminalApp
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
LaunchMode GetLaunchMode();
Boolean GetShowTabsInTitlebar();
Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension);
void TitlebarClicked();
void WindowCloseButtonClicked();

Expand Down
427 changes: 372 additions & 55 deletions src/cascadia/TerminalApp/Pane.cpp

Large diffs are not rendered by default.

57 changes: 52 additions & 5 deletions src/cascadia/TerminalApp/Pane.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,26 @@ class Pane : public std::enable_shared_from_this<Pane>
void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings,
const GUID& profile);
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void Relayout();
bool ResizePane(const winrt::TerminalApp::Direction& direction);
bool NavigateFocus(const winrt::TerminalApp::Direction& direction);

bool CanSplit(SplitState splitType);
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(SplitState splitType,
const GUID& profile,
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;

void Close();

WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);

private:
struct SnapSizeResult;
struct SnapChildrenSizeResult;
struct LayoutSizeNode;

winrt::Windows::UI::Xaml::Controls::Grid _root{};
winrt::Windows::UI::Xaml::Controls::Border _border{};
winrt::Microsoft::Terminal::TerminalControl::TermControl _control{ nullptr };
Expand All @@ -84,8 +90,7 @@ class Pane : public std::enable_shared_from_this<Pane>
std::shared_ptr<Pane> _firstChild{ nullptr };
std::shared_ptr<Pane> _secondChild{ nullptr };
SplitState _splitState{ SplitState::None };
std::optional<float> _firstPercent{ std::nullopt };
std::optional<float> _secondPercent{ std::nullopt };
float _desiredSplitPosition;

bool _lastActive{ false };
std::optional<GUID> _profile{ std::nullopt };
Expand Down Expand Up @@ -120,12 +125,17 @@ class Pane : public std::enable_shared_from_this<Pane>

void _FocusFirstChild();
void _ControlConnectionStateChangedHandler(const winrt::Microsoft::Terminal::TerminalControl::TermControl& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);

std::pair<float, float> _GetPaneSizes(const float& fullSize);
std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
SnapSizeResult _CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
void _AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& sizeNode) const;

winrt::Windows::Foundation::Size _GetMinSize() const;
void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
LayoutSizeNode _CreateMinSizeTree(const bool widthOrHeight) const;
float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const;

// Function Description:
// - Returns true if the given direction can be used with the given split
Expand Down Expand Up @@ -162,4 +172,41 @@ class Pane : public std::enable_shared_from_this<Pane>
}

static void _SetupResources();

struct SnapSizeResult
{
float lower;
float higher;
};

struct SnapChildrenSizeResult
{
std::pair<float, float> lower;
std::pair<float, float> higher;
};

// Helper structure that builds a (roughly) binary tree corresponding
// to the pane tree. Used for layouting panes with snapped sizes.
struct LayoutSizeNode
{
float size;
bool isMinimumSize;
std::unique_ptr<LayoutSizeNode> firstChild;
std::unique_ptr<LayoutSizeNode> secondChild;

// These two fields hold next possible snapped values of firstChild and
// secondChild. Although that could be calculated from these fields themself,
// it would be wasteful as we have to know these values more often than for
// simple increment. Hence we cache that here.
std::unique_ptr<LayoutSizeNode> nextFirstChild;
mcpiroman marked this conversation as resolved.
Show resolved Hide resolved
std::unique_ptr<LayoutSizeNode> nextSecondChild;

LayoutSizeNode(const float minSize);
LayoutSizeNode(const LayoutSizeNode& other);

LayoutSizeNode& operator=(const LayoutSizeNode& other);

private:
void _AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode);
};
};
21 changes: 21 additions & 0 deletions src/cascadia/TerminalApp/Tab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ void Tab::SplitPane(Pane::SplitState splitType, const GUID& profile, TermControl
_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.
Expand Down Expand Up @@ -303,6 +310,20 @@ void Tab::_AttachEventHandlersToControl(const TermControl& control)
auto newTabTitle = GetActiveTitle();
SetTabText(newTabTitle);
});

// 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:
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/Tab.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class Tab
bool CanSplitPane(Pane::SplitState splitType);
void SplitPane(Pane::SplitState splitType, const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);

float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;

void UpdateIcon(const winrt::hstring iconPath);

void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
Expand Down
8 changes: 8 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,14 @@ namespace winrt::TerminalApp::implementation
}
}

// Method Description:
// - See Pane::CalcSnappedDimension
float TerminalPage::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const
{
const auto focusedTabIndex = _GetFocusedTabIndex();
return _tabs[focusedTabIndex]->CalcSnappedDimension(widthOrHeight, dimension);
}

// Method Description:
// - Place `copiedData` into the clipboard as text. Triggered when a
// terminal control raises it's CopyToClipboard event.
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ namespace winrt::TerminalApp::implementation

void TitlebarClicked();

float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;

void CloseWindow();

// -------------------------------- WinRT Events ---------------------------------
Expand Down
73 changes: 73 additions & 0 deletions src/cascadia/TerminalApp/lib/Pane.LayoutSizeNode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"
#include "Pane.h"

Pane::LayoutSizeNode::LayoutSizeNode(const float minSize) :
size{ minSize },
isMinimumSize{ true },
firstChild{ nullptr },
secondChild{ nullptr },
nextFirstChild{ nullptr },
nextSecondChild{ nullptr }
{
}

Pane::LayoutSizeNode::LayoutSizeNode(const LayoutSizeNode& other) :
size{ other.size },
isMinimumSize{ other.isMinimumSize },
firstChild{ other.firstChild ? std::make_unique<LayoutSizeNode>(*other.firstChild) : nullptr },
secondChild{ other.secondChild ? std::make_unique<LayoutSizeNode>(*other.secondChild) : nullptr },
nextFirstChild{ other.nextFirstChild ? std::make_unique<LayoutSizeNode>(*other.nextFirstChild) : nullptr },
nextSecondChild{ other.nextSecondChild ? std::make_unique<LayoutSizeNode>(*other.nextSecondChild) : nullptr }
{
}

// Method Description:
// - Makes sure that this node and all its descendants equal the supplied node.
// This may be more efficient that copy construction since it will reuse its
// allocated children.
// Arguments:
// - other: Node to take the values from.
// Return Value:
// - itself
Pane::LayoutSizeNode& Pane::LayoutSizeNode::operator=(const LayoutSizeNode& other)
{
size = other.size;
isMinimumSize = other.isMinimumSize;

_AssignChildNode(firstChild, other.firstChild.get());
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems like dangerous use of unique pointers. you're sharing ownership by popping the pointers out of them, but the first one of these things to get destructed is going to explode the entire tree of them by deleting the child nodes.

Copy link
Contributor

Choose a reason for hiding this comment

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

(like: two unique_ptrs holding the same underlying pointer are never ever ever okay)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I don't understand what's wrong here. I only pop the underlying pointer to pass it to _AssignChildNode, which just reads the pointed object and copies it (copies object, not the pointer). It could receive it by reference as well, except it has to be nullable. Or it could receive reference to unique_ptr but that's anti pattern in such situations (not to force implementation). And I don't see any two unique_ptrs to hold the same value.

_AssignChildNode(secondChild, other.secondChild.get());
_AssignChildNode(nextFirstChild, other.nextFirstChild.get());
_AssignChildNode(nextSecondChild, other.nextSecondChild.get());

return *this;
}

// Method Description:
// - Performs assignment operation on a single child node reusing
// - current one if present.
// Arguments:
// - nodeField: Reference to our field holding concerned node.
// - other: Node to take the values from.
// Return Value:
// - <none>
void Pane::LayoutSizeNode::_AssignChildNode(std::unique_ptr<LayoutSizeNode>& nodeField, const LayoutSizeNode* const newNode)
{
if (newNode)
{
if (nodeField)
{
*nodeField = *newNode;
}
else
{
nodeField.reset(new LayoutSizeNode(*newNode));
mcpiroman marked this conversation as resolved.
Show resolved Hide resolved
}
}
else
{
nodeField.release();
}
}
3 changes: 2 additions & 1 deletion src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
<ClCompile Include="../PowershellCoreProfileGenerator.cpp" />
<ClCompile Include="../WslDistroGenerator.cpp" />
<ClCompile Include="../AzureCloudShellGenerator.cpp" />
<ClCompile Include="Pane.LayoutSizeNode.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
Expand Down Expand Up @@ -358,4 +359,4 @@
<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>
45 changes: 40 additions & 5 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
// and react accordingly.
_UpdateFont();
_UpdateFont(true);

const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };

Expand Down Expand Up @@ -1282,7 +1282,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// font change. This method will *not* change the buffer/viewport size
// to account for the new glyph dimensions. Callers should make sure to
// appropriately call _DoResize after this method is called.
void TermControl::_UpdateFont()
// Arguments:
// - initialUpdate: whether this font update should be considered as being
// concerned with initialization process. Value forwarded to event handler.
void TermControl::_UpdateFont(const bool initialUpdate)
{
auto lock = _terminal->LockForWriting();

Expand All @@ -1291,6 +1294,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);

const auto actualNewSize = _actualFont.GetSize();
_fontSizeChangedHandlers(actualNewSize.X, actualNewSize.Y, initialUpdate);
}

// Method Description:
Expand Down Expand Up @@ -1756,13 +1762,41 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}

// Account for the size of any padding
auto thickness = _ParseThicknessFromPadding(_settings.Padding());
width += thickness.Left + thickness.Right;
height += thickness.Top + thickness.Bottom;
const auto padding = _swapChainPanel.Margin();
width += padding.Left + padding.Right;
height += padding.Top + padding.Bottom;

return { gsl::narrow_cast<float>(width), gsl::narrow_cast<float>(height) };
}

// Method Description:
// - Adjusts given dimension (width or height) so that it aligns to the character grid.
// The snap is always downward.
// Arguments:
// - widthOrHeight: if true operates on width, otherwise on height
// - dimension: a dimension (width or height) to be snapped
// Return Value:
// - A dimension that would be aligned to the character grid.
float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const
Copy link
Member

Choose a reason for hiding this comment

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

Reading this method, I'd maybe want this first param named something like

Suggested change
float TermControl::SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const
float TermControl::SnapDimensionToGrid(const bool snapToWidth, const float dimension) const

instead, especially lower:

if (snapToWidth && _settings.ScrollState() == ScrollbarState::Visible)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If so, this should be renamed everywhere (so in a lot of places). I went with widthOrHeight because I find it more readable. With some thing like isWidth, it is not so apparent what it means if it's false. Usually not-width means height, but not always.

{
const auto fontSize = _actualFont.GetSize();
const auto fontDimension = widthOrHeight ? fontSize.X : fontSize.Y;

const auto padding = _swapChainPanel.Margin();
auto nonTerminalArea = gsl::narrow_cast<float>(widthOrHeight ?
padding.Left + padding.Right :
padding.Top + padding.Bottom);

if (widthOrHeight && _settings.ScrollState() == ScrollbarState::Visible)
{
nonTerminalArea += gsl::narrow_cast<float>(_scrollBar.ActualWidth());
}

const auto gridSize = dimension - nonTerminalArea;
const int cells = static_cast<int>(gridSize / fontDimension);
return cells * fontDimension + nonTerminalArea;
}

// Method Description:
// - Create XAML Thickness object based on padding props provided.
// Used for controlling the TermControl XAML Grid container's Padding prop.
Expand Down Expand Up @@ -1978,6 +2012,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Winrt events need a method for adding a callback to the event and removing the callback.
// These macros will define them both for you.
DEFINE_EVENT(TermControl, TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
DEFINE_EVENT(TermControl, FontSizeChanged, _fontSizeChangedHandlers, TerminalControl::FontSizeChangedEventArgs);
DEFINE_EVENT(TermControl, ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);

DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TermControl, PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs);
Expand Down
4 changes: 3 additions & 1 deletion src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void Close();
Windows::Foundation::Size CharacterDimensions() const;
Windows::Foundation::Size MinimumSize() const;
float SnapDimensionToGrid(const bool widthOrHeight, const float dimension) const;

void ScrollViewport(int viewTop);
void KeyboardScrollViewport(int viewTop);
Expand All @@ -88,6 +89,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// clang-format off
// -------------------------------- WinRT Events ---------------------------------
DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
DECLARE_EVENT(FontSizeChanged, _fontSizeChangedHandlers, TerminalControl::FontSizeChangedEventArgs);
DECLARE_EVENT(ScrollPositionChanged, _scrollPositionChangedHandlers, TerminalControl::ScrollPositionChangedEventArgs);

DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(PasteFromClipboard, _clipboardPasteHandlers, TerminalControl::TermControl, TerminalControl::PasteFromClipboardEventArgs);
Expand Down Expand Up @@ -160,7 +162,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _InitializeBackgroundBrush();
void _BackgroundColorChanged(const uint32_t color);
bool _InitializeTerminal();
void _UpdateFont();
void _UpdateFont(const bool initialUpdate = false);
void _SetFontSize(int fontSize);
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _CharacterHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e);
Expand Down
Loading