Skip to content

Commit

Permalink
wpf: Add AutoFill to control whether the connection/buffer resizes (m…
Browse files Browse the repository at this point in the history
…icrosoft#7853)

Adds the ability to manually handle the terminal renderer resizing
events by allowing different render size and WPF control size. This is
done by adding an `AutoFill` property to the control that prevents the
renderer from automatically resizing and tells the WPF control to fill
in the extra space with the terminal background as shown below:

This PR adds the following:
- Helper method in the DX engine to convert character viewports into
  pixel viewports
- `AutoFill` property that prevents automatic resizing of the renderer
- Tweaks and fixes that automatically fill in the empty space if
  `AutoFill` is set to false
- Fixes resizing methods and streamlines their codepath

## Validation Steps Performed
Manual validation with the Visual Studio Integrated Terminal tool
window.
  • Loading branch information
javierdlg authored and skyline75489 committed Oct 10, 2020
1 parent a17306a commit 0ef2fb0
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 58 deletions.
69 changes: 55 additions & 14 deletions src/cascadia/PublicTerminalCore/HwndTerminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,15 @@ void _stdcall TerminalSendOutput(void* terminal, LPCWSTR data)
publicTerminal->SendOutput(data);
}

HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double height, _Out_ COORD* dimensions)
/// <summary>
/// Triggers a terminal resize using the new width and height in pixel.
/// </summary>
/// <param name="terminal">Terminal pointer.</param>
/// <param name="width">New width of the terminal in pixels.</param>
/// <param name="height">New height of the terminal in pixels</param>
/// <param name="dimensions">Out parameter containing the columns and rows that fit the new size.</param>
/// <returns>HRESULT of the attempted resize.</returns>
HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);

Expand All @@ -446,10 +454,55 @@ HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double heig
static_cast<int>(height),
0));

const SIZE windowSize{ static_cast<short>(width), static_cast<short>(height) };
const SIZE windowSize{ width, height };
return publicTerminal->Refresh(windowSize, dimensions);
}

/// <summary>
/// Helper method for resizing the terminal using character column and row counts
/// </summary>
/// <param name="terminal">Pointer to the terminal object.</param>
/// <param name="dimensionsInCharacters">New terminal size in row and column count.</param>
/// <param name="dimensionsInPixels">Out parameter with the new size of the renderer.</param>
/// <returns>HRESULT of the attempted resize.</returns>
HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ COORD dimensionsInCharacters, _Out_ SIZE* dimensionsInPixels)
{
RETURN_HR_IF_NULL(E_INVALIDARG, dimensionsInPixels);

const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);

const auto viewInCharacters = Viewport::FromDimensions({ 0, 0 }, { (dimensionsInCharacters.X), (dimensionsInCharacters.Y) });
const auto viewInPixels = publicTerminal->_renderEngine->GetViewportInPixels(viewInCharacters);

dimensionsInPixels->cx = viewInPixels.Width();
dimensionsInPixels->cy = viewInPixels.Height();

COORD unused{ 0, 0 };

return TerminalTriggerResize(terminal, viewInPixels.Width(), viewInPixels.Height(), &unused);
}

/// <summary>
/// Calculates the amount of rows and columns that fit in the provided width and height.
/// </summary>
/// <param name="terminal">Terminal pointer</param>
/// <param name="width">Width of the terminal area to calculate.</param>
/// <param name="height">Height of the terminal area to calculate.</param>
/// <param name="dimensions">Out parameter containing the columns and rows that fit the new size.</param>
/// <returns>HRESULT of the calculation.</returns>
HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);

const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, { width, height });
const auto viewInCharacters = publicTerminal->_renderEngine->GetViewportInCharacters(viewInPixels);

dimensions->X = viewInCharacters.Width();
dimensions->Y = viewInCharacters.Height();

return S_OK;
}

void _stdcall TerminalDpiChanged(void* terminal, int newDpi)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
Expand Down Expand Up @@ -760,18 +813,6 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
publicTerminal->Refresh(windowSize, &dimensions);
}

// Resizes the terminal to the specified rows and columns.
HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);

auto lock = publicTerminal->_terminal->LockForWriting();
publicTerminal->_terminal->ClearSelection();
publicTerminal->_renderer->TriggerRedrawAll();

return publicTerminal->_terminal->UserResize(dimensions);
}

void _stdcall TerminalBlinkCursor(void* terminal)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
Expand Down
9 changes: 6 additions & 3 deletions src/cascadia/PublicTerminalCore/HwndTerminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ extern "C" {
__declspec(dllexport) HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal);
__declspec(dllexport) void _stdcall TerminalSendOutput(void* terminal, LPCWSTR data);
__declspec(dllexport) void _stdcall TerminalRegisterScrollCallback(void* terminal, void __stdcall callback(int, int, int));
__declspec(dllexport) HRESULT _stdcall TerminalTriggerResize(void* terminal, double width, double height, _Out_ COORD* dimensions);
__declspec(dllexport) HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
__declspec(dllexport) HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
__declspec(dllexport) HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ COORD dimensions, _Out_ SIZE* dimensionsInPixels);
__declspec(dllexport) HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
__declspec(dllexport) void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
__declspec(dllexport) void _stdcall TerminalUserScroll(void* terminal, int viewTop);
__declspec(dllexport) void _stdcall TerminalClearSelection(void* terminal);
Expand Down Expand Up @@ -90,7 +91,9 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
std::optional<til::point> _singleClickTouchdownPos;

friend HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal);
friend HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
friend HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
friend HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ COORD dimensions, _Out_ SIZE* dimensionsInPixels);
friend HRESULT _stdcall TerminalCalculateResize(_In_ void* terminal, _In_ short width, _In_ short height, _Out_ COORD* dimensions);
friend void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
friend void _stdcall TerminalUserScroll(void* terminal, int viewTop);
friend void _stdcall TerminalClearSelection(void* terminal);
Expand Down
28 changes: 22 additions & 6 deletions src/cascadia/WpfTerminalControl/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ namespace Microsoft.Terminal.Wpf
{
using System;
using System.Runtime.InteropServices;
using System.Windows.Automation.Provider;

#pragma warning disable SA1600 // Elements should be documented
internal static class NativeMethods
Expand Down Expand Up @@ -187,10 +186,13 @@ public enum SetWindowPosFlags : uint
public static extern void TerminalSendOutput(IntPtr terminal, string lpdata);

[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern uint TerminalTriggerResize(IntPtr terminal, double width, double height, out COORD dimensions);
public static extern uint TerminalTriggerResize(IntPtr terminal, short width, short height, out COORD dimensions);

[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern uint TerminalResize(IntPtr terminal, COORD dimensions);
public static extern uint TerminalTriggerResizeWithDimension(IntPtr terminal, [MarshalAs(UnmanagedType.Struct)] COORD dimensions, out SIZE dimensionsInPixels);

[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern uint TerminalCalculateResize(IntPtr terminal, short width, short height, out COORD dimensions);

[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern void TerminalDpiChanged(IntPtr terminal, int newDpi);
Expand All @@ -205,10 +207,10 @@ public enum SetWindowPosFlags : uint
public static extern void TerminalUserScroll(IntPtr terminal, int viewTop);

[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern uint TerminalStartSelection(IntPtr terminal, NativeMethods.COORD cursorPosition, bool altPressed);
public static extern uint TerminalStartSelection(IntPtr terminal, COORD cursorPosition, bool altPressed);

[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern uint TerminalMoveSelection(IntPtr terminal, NativeMethods.COORD cursorPosition);
public static extern uint TerminalMoveSelection(IntPtr terminal, COORD cursorPosition);

[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern void TerminalClearSelection(IntPtr terminal);
Expand Down Expand Up @@ -278,10 +280,24 @@ public struct COORD
public short X;

/// <summary>
/// The x-coordinate of the point.
/// The y-coordinate of the point.
/// </summary>
public short Y;
}

[StructLayout(LayoutKind.Sequential)]
public struct SIZE
{
/// <summary>
/// The x size.
/// </summary>
public int cx;

/// <summary>
/// The y size.
/// </summary>
public int cy;
}
}
#pragma warning restore SA1600 // Elements should be documented
}
105 changes: 77 additions & 28 deletions src/cascadia/WpfTerminalControl/TerminalContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,6 @@ namespace Microsoft.Terminal.Wpf
/// </remarks>
public class TerminalContainer : HwndHost
{
private static void UnpackKeyMessage(IntPtr wParam, IntPtr lParam, out ushort vkey, out ushort scanCode, out ushort flags)
{
ulong scanCodeAndFlags = (((ulong)lParam) & 0xFFFF0000) >> 16;
scanCode = (ushort)(scanCodeAndFlags & 0x00FFu);
flags = (ushort)(scanCodeAndFlags & 0xFF00u);
vkey = (ushort)wParam;
}

private static void UnpackCharMessage(IntPtr wParam, IntPtr lParam, out char character, out ushort scanCode, out ushort flags)
{
UnpackKeyMessage(wParam, lParam, out ushort vKey, out scanCode, out flags);
character = (char)vKey;
}

private ITerminalConnection connection;
private IntPtr hwnd;
private IntPtr terminal;
Expand Down Expand Up @@ -77,15 +63,33 @@ public TerminalContainer()
internal event EventHandler<int> UserScrolled;

/// <summary>
/// Gets the character rows available to the terminal.
/// Gets or sets a value indicating whether if the renderer should automatically resize to fill the control
/// on user action.
/// </summary>
public bool AutoFill { get; set; } = true;

/// <summary>
/// Gets the current character rows available to the terminal.
/// </summary>
internal int Rows { get; private set; }

/// <summary>
/// Gets the character columns available to the terminal.
/// Gets the current character columns available to the terminal.
/// </summary>
internal int Columns { get; private set; }

/// <summary>
/// Gets the maximum amount of character rows that can fit in this control.
/// </summary>
/// <remarks>This will be in sync with <see cref="Rows"/> unless <see cref="AutoFill"/> is set to false.</remarks>
internal int MaxRows { get; private set; }

/// <summary>
/// Gets the maximum amount of character columns that can fit in this control.
/// </summary>
/// <remarks>This will be in sync with <see cref="Columns"/> unless <see cref="AutoFill"/> is set to false.</remarks>
internal int MaxColumns { get; private set; }

/// <summary>
/// Gets the window handle of the terminal.
/// </summary>
Expand Down Expand Up @@ -162,34 +166,50 @@ internal string GetSelectedText()
var dpiScale = VisualTreeHelper.GetDpi(this);

NativeMethods.COORD dimensions;
NativeMethods.TerminalTriggerResize(this.terminal, renderSize.Width * dpiScale.DpiScaleX, renderSize.Height * dpiScale.DpiScaleY, out dimensions);
NativeMethods.TerminalTriggerResize(
this.terminal,
Convert.ToInt16(renderSize.Width * dpiScale.DpiScaleX),
Convert.ToInt16(renderSize.Height * dpiScale.DpiScaleY),
out dimensions);

this.Rows = dimensions.Y;
this.Columns = dimensions.X;

this.connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
return (dimensions.Y, dimensions.X);
}

/// <summary>
/// Resizes the terminal.
/// Resizes the terminal using row and column count as the new size.
/// </summary>
/// <param name="rows">Number of rows to show.</param>
/// <param name="columns">Number of columns to show.</param>
internal void Resize(uint rows, uint columns)
/// <returns><see cref="long"/> pair with the new width and height size in pixels for the renderer.</returns>
internal (int width, int height) Resize(uint rows, uint columns)
{
NativeMethods.SIZE dimensionsInPixels;
NativeMethods.COORD dimensions = new NativeMethods.COORD
{
X = (short)columns,
Y = (short)rows,
};

NativeMethods.TerminalResize(this.terminal, dimensions);
NativeMethods.TerminalTriggerResizeWithDimension(this.terminal, dimensions, out dimensionsInPixels);

// If AutoFill is true, keep Rows and Columns in sync with MaxRows and MaxColumns.
// Otherwise, MaxRows and MaxColumns will be set on startup and on control resize by the user.
if (this.AutoFill)
{
this.MaxColumns = dimensions.X;
this.MaxRows = dimensions.Y;
}

this.Rows = dimensions.Y;
this.Columns = dimensions.X;
this.Rows = dimensions.Y;

this.connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);

return (dimensionsInPixels.cx, dimensionsInPixels.cy);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -238,6 +258,20 @@ protected override void DestroyWindowCore(HandleRef hwnd)
this.terminal = IntPtr.Zero;
}

private static void UnpackKeyMessage(IntPtr wParam, IntPtr lParam, out ushort vkey, out ushort scanCode, out ushort flags)
{
ulong scanCodeAndFlags = (((ulong)lParam) & 0xFFFF0000) >> 16;
scanCode = (ushort)(scanCodeAndFlags & 0x00FFu);
flags = (ushort)(scanCodeAndFlags & 0xFF00u);
vkey = (ushort)wParam;
}

private static void UnpackCharMessage(IntPtr wParam, IntPtr lParam, out char character, out ushort scanCode, out ushort flags)
{
UnpackKeyMessage(wParam, lParam, out ushort vKey, out scanCode, out flags);
character = (char)vKey;
}

private void TerminalContainer_GotFocus(object sender, RoutedEventArgs e)
{
e.Handled = true;
Expand Down Expand Up @@ -299,13 +333,28 @@ private IntPtr TerminalContainer_MessageHook(IntPtr hwnd, int msg, IntPtr wParam
break;
}

NativeMethods.TerminalTriggerResize(this.terminal, windowpos.cx, windowpos.cy, out var dimensions);
NativeMethods.COORD dimensions;

this.connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
this.Columns = dimensions.X;
this.Rows = dimensions.Y;
// We only trigger a resize if we want to fill to maximum size.
if (this.AutoFill)
{
NativeMethods.TerminalTriggerResize(this.terminal, (short)windowpos.cx, (short)windowpos.cy, out dimensions);

this.Columns = dimensions.X;
this.Rows = dimensions.Y;
this.MaxColumns = dimensions.X;
this.MaxRows = dimensions.Y;
}
else
{
NativeMethods.TerminalCalculateResize(this.terminal, (short)windowpos.cx, (short)windowpos.cy, out dimensions);
this.MaxColumns = dimensions.X;
this.MaxRows = dimensions.Y;
}

this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
break;

case NativeMethods.WindowMessage.WM_MOUSEWHEEL:
var delta = (short)(((long)wParam) >> 16);
this.UserScrolled?.Invoke(this, delta);
Expand Down Expand Up @@ -360,7 +409,7 @@ private void OnScroll(int viewTop, int viewHeight, int bufferSize)

private void OnWrite(string data)
{
this.connection?.WriteInput(data);
this.Connection?.WriteInput(data);
}
}
}
5 changes: 3 additions & 2 deletions src/cascadia/WpfTerminalControl/TerminalControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
xmlns:local="clr-namespace:Microsoft.Terminal.Wpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Focusable="True">
<Grid>
Focusable="True"
x:Name="terminalUserControl">
<Grid x:Name="terminalGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto"/>
Expand Down
Loading

0 comments on commit 0ef2fb0

Please sign in to comment.