Skip to content

Commit

Permalink
wpf: Add AutoFill to control whether the connection/buffer resizes (#…
Browse files Browse the repository at this point in the history
…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 Oct 9, 2020
1 parent cd76893 commit 9e86e29
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 9e86e29

Please sign in to comment.