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

wpf: Add AutoFill to control whether the connection/buffer resizes #7853

Merged
9 commits merged into from
Oct 9, 2020
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