wpf: fix margin calculations and resize events (#7892)

This commit is contained in:
Javier 2020-10-12 18:21:11 -07:00 committed by GitHub
parent cb96aa718f
commit d2d462fc48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 145 additions and 88 deletions

View File

@ -32,6 +32,7 @@ tasklist
tdbuildteamid tdbuildteamid
vcruntime vcruntime
visualstudio visualstudio
VSTHRD
wlk wlk
wslpath wslpath
wtl wtl

View File

@ -66,7 +66,18 @@ namespace Microsoft.Terminal.Wpf
/// Gets or sets a value indicating whether if the renderer should automatically resize to fill the control /// Gets or sets a value indicating whether if the renderer should automatically resize to fill the control
/// on user action. /// on user action.
/// </summary> /// </summary>
public bool AutoFill { get; set; } = true; internal bool AutoResize { get; set; } = true;
/// <summary>
/// Gets or sets the size of the parent user control that hosts the terminal hwnd.
/// </summary>
/// <remarks>Control size is in device independent units, but for simplicity all sizes should be scaled.</remarks>
internal Size TerminalControlSize { get; set; }
/// <summary>
/// Gets or sets the size of the terminal renderer.
/// </summary>
internal Size TerminalRendererSize { get; set; }
/// <summary> /// <summary>
/// Gets the current character rows available to the terminal. /// Gets the current character rows available to the terminal.
@ -78,18 +89,6 @@ namespace Microsoft.Terminal.Wpf
/// </summary> /// </summary>
internal int Columns { get; private set; } 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> /// <summary>
/// Gets the window handle of the terminal. /// Gets the window handle of the terminal.
/// </summary> /// </summary>
@ -139,7 +138,7 @@ namespace Microsoft.Terminal.Wpf
NativeMethods.TerminalSetTheme(this.terminal, theme, fontFamily, fontSize, (int)dpiScale.PixelsPerInchX); NativeMethods.TerminalSetTheme(this.terminal, theme, fontFamily, fontSize, (int)dpiScale.PixelsPerInchX);
this.TriggerResize(this.RenderSize); this.Resize(this.TerminalControlSize);
} }
/// <summary> /// <summary>
@ -157,26 +156,22 @@ namespace Microsoft.Terminal.Wpf
} }
/// <summary> /// <summary>
/// Triggers a refresh of the terminal with the given size. /// Triggers a resize of the terminal with the given size, redrawing the rendered text.
/// </summary> /// </summary>
/// <param name="renderSize">Size of the rendering window.</param> /// <param name="renderSize">Size of the rendering window.</param>
/// <returns>Tuple with rows and columns.</returns> internal void Resize(Size renderSize)
internal (int rows, int columns) TriggerResize(Size renderSize)
{ {
var dpiScale = VisualTreeHelper.GetDpi(this);
NativeMethods.COORD dimensions;
NativeMethods.TerminalTriggerResize( NativeMethods.TerminalTriggerResize(
this.terminal, this.terminal,
Convert.ToInt16(renderSize.Width * dpiScale.DpiScaleX), Convert.ToInt16(renderSize.Width),
Convert.ToInt16(renderSize.Height * dpiScale.DpiScaleY), Convert.ToInt16(renderSize.Height),
out dimensions); out NativeMethods.COORD dimensions);
this.Rows = dimensions.Y; this.Rows = dimensions.Y;
this.Columns = dimensions.X; this.Columns = dimensions.X;
this.TerminalRendererSize = renderSize;
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X); this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
return (dimensions.Y, dimensions.X);
} }
/// <summary> /// <summary>
@ -184,8 +179,7 @@ namespace Microsoft.Terminal.Wpf
/// </summary> /// </summary>
/// <param name="rows">Number of rows to show.</param> /// <param name="rows">Number of rows to show.</param>
/// <param name="columns">Number of columns to show.</param> /// <param name="columns">Number of columns to show.</param>
/// <returns><see cref="long"/> pair with the new width and height size in pixels for the renderer.</returns> internal void Resize(uint rows, uint columns)
internal (int width, int height) Resize(uint rows, uint columns)
{ {
NativeMethods.SIZE dimensionsInPixels; NativeMethods.SIZE dimensionsInPixels;
NativeMethods.COORD dimensions = new NativeMethods.COORD NativeMethods.COORD dimensions = new NativeMethods.COORD
@ -196,20 +190,41 @@ namespace Microsoft.Terminal.Wpf
NativeMethods.TerminalTriggerResizeWithDimension(this.terminal, dimensions, out dimensionsInPixels); 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.Columns = dimensions.X; this.Columns = dimensions.X;
this.Rows = dimensions.Y; this.Rows = dimensions.Y;
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X); this.TerminalRendererSize = new Size()
{
Width = dimensionsInPixels.cx,
Height = dimensionsInPixels.cy,
};
return (dimensionsInPixels.cx, dimensionsInPixels.cy); this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
}
/// <summary>
/// Calculates the rows and columns that would fit in the given size.
/// </summary>
/// <param name="size">DPI scaled size.</param>
/// <returns>Amount of rows and columns that would fit the given size.</returns>
internal (uint columns, uint rows) CalculateRowsAndColumns(Size size)
{
NativeMethods.TerminalCalculateResize(this.terminal, (short)size.Width, (short)size.Height, out NativeMethods.COORD dimensions);
return ((uint)dimensions.X, (uint)dimensions.Y);
}
/// <summary>
/// Triggers the terminal resize event if more space is available in the terminal control.
/// </summary>
internal void RaiseResizedIfDrawSpaceIncreased()
{
(var columns, var rows) = this.CalculateRowsAndColumns(this.TerminalControlSize);
if (this.Columns < columns || this.Rows < rows)
{
this.connection?.Resize(rows, columns);
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -335,21 +350,23 @@ namespace Microsoft.Terminal.Wpf
NativeMethods.COORD dimensions; NativeMethods.COORD dimensions;
// We only trigger a resize if we want to fill to maximum size. if (this.AutoResize)
if (this.AutoFill)
{ {
NativeMethods.TerminalTriggerResize(this.terminal, (short)windowpos.cx, (short)windowpos.cy, out dimensions); NativeMethods.TerminalTriggerResize(this.terminal, (short)windowpos.cx, (short)windowpos.cy, out dimensions);
this.Columns = dimensions.X; this.Columns = dimensions.X;
this.Rows = dimensions.Y; this.Rows = dimensions.Y;
this.MaxColumns = dimensions.X;
this.MaxRows = dimensions.Y; this.TerminalRendererSize = new Size()
{
Width = windowpos.cx,
Height = windowpos.cy,
};
} }
else else
{ {
NativeMethods.TerminalCalculateResize(this.terminal, (short)windowpos.cx, (short)windowpos.cy, out dimensions); // Calculate the new columns and rows that fit the total control size and alert the control to redraw the margins.
this.MaxColumns = dimensions.X; NativeMethods.TerminalCalculateResize(this.terminal, (short)this.TerminalControlSize.Width, (short)this.TerminalControlSize.Height, out dimensions);
this.MaxRows = dimensions.Y;
} }
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X); this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);

View File

@ -6,10 +6,12 @@
namespace Microsoft.Terminal.Wpf namespace Microsoft.Terminal.Wpf
{ {
using System; using System;
using System.Threading;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using Task = System.Threading.Tasks.Task;
/// <summary> /// <summary>
/// A basic terminal control. This control can receive and render standard VT100 sequences. /// A basic terminal control. This control can receive and render standard VT100 sequences.
@ -18,7 +20,13 @@ namespace Microsoft.Terminal.Wpf
{ {
private int accumulatedDelta = 0; private int accumulatedDelta = 0;
private (int width, int height) terminalRendererSize; /// <summary>
/// Gets size of the terminal renderer.
/// </summary>
private Size TerminalRendererSize
{
get => this.termContainer.TerminalRendererSize;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TerminalControl"/> class. /// Initializes a new instance of the <see cref="TerminalControl"/> class.
@ -44,24 +52,14 @@ namespace Microsoft.Terminal.Wpf
/// </summary> /// </summary>
public int Columns => this.termContainer.Columns; public int Columns => this.termContainer.Columns;
/// <summary>
/// Gets the maximum amount of character rows that can fit in this control.
/// </summary>
public int MaxRows => this.termContainer.MaxRows;
/// <summary>
/// Gets the maximum amount of character columns that can fit in this control.
/// </summary>
public int MaxColumns => this.termContainer.MaxColumns;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether if the renderer should automatically resize to fill the control /// Gets or sets a value indicating whether if the renderer should automatically resize to fill the control
/// on user action. /// on user action.
/// </summary> /// </summary>
public bool AutoFill public bool AutoResize
{ {
get => this.termContainer.AutoFill; get => this.termContainer.AutoResize;
set => this.termContainer.AutoFill = value; set => this.termContainer.AutoResize = value;
} }
/// <summary> /// <summary>
@ -69,10 +67,7 @@ namespace Microsoft.Terminal.Wpf
/// </summary> /// </summary>
public ITerminalConnection Connection public ITerminalConnection Connection
{ {
set set => this.termContainer.Connection = value;
{
this.termContainer.Connection = value;
}
} }
/// <summary> /// <summary>
@ -114,56 +109,100 @@ namespace Microsoft.Terminal.Wpf
/// </summary> /// </summary>
/// <param name="rows">Number of rows to display.</param> /// <param name="rows">Number of rows to display.</param>
/// <param name="columns">Number of columns to display.</param> /// <param name="columns">Number of columns to display.</param>
public void Resize(uint rows, uint columns) /// <param name="cancellationToken">Cancellation token for this task.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task ResizeAsync(uint rows, uint columns, CancellationToken cancellationToken)
{ {
var dpiScale = VisualTreeHelper.GetDpi(this); this.termContainer.Resize(rows, columns);
this.terminalRendererSize = this.termContainer.Resize(rows, columns); #pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs
await this.Dispatcher.BeginInvoke(
double marginWidth = ((this.terminalUserControl.RenderSize.Width * dpiScale.DpiScaleX) - this.terminalRendererSize.width) / dpiScale.DpiScaleX; new Action(delegate() { this.terminalGrid.Margin = this.CalculateMargins(); }),
double marginHeight = ((this.terminalUserControl.RenderSize.Height * dpiScale.DpiScaleY) - this.terminalRendererSize.height) / dpiScale.DpiScaleY; System.Windows.Threading.DispatcherPriority.Render);
#pragma warning restore VSTHRD001 // Avoid legacy thread switching APIs
// Make space for the scrollbar.
marginWidth -= this.scrollbar.Width;
// Prevent negative margin size.
marginWidth = marginWidth < 0 ? 0 : marginWidth;
marginHeight = marginHeight < 0 ? 0 : marginHeight;
this.terminalGrid.Margin = new Thickness(0, 0, marginWidth, marginHeight);
} }
/// <summary> /// <summary>
/// Resizes the terminal to the specified dimensions. /// Resizes the terminal to the specified dimensions.
/// </summary> /// </summary>
/// <param name="rendersize">Rendering size for the terminal.</param> /// <param name="rendersize">Rendering size for the terminal in device independent units.</param>
/// <returns>A tuple of (int, int) representing the number of rows and columns in the terminal.</returns> /// <returns>A tuple of (int, int) representing the number of rows and columns in the terminal.</returns>
public (int rows, int columns) TriggerResize(Size rendersize) public (int rows, int columns) TriggerResize(Size rendersize)
{ {
return this.termContainer.TriggerResize(rendersize); var dpiScale = VisualTreeHelper.GetDpi(this);
rendersize.Width *= dpiScale.DpiScaleX;
rendersize.Height *= dpiScale.DpiScaleY;
this.termContainer.Resize(rendersize);
return (this.Rows, this.Columns);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{ {
// Renderer will not resize on control resize. We have to manually recalculate the margin to fill in the space. var dpiScale = VisualTreeHelper.GetDpi(this);
if (this.AutoFill == false && this.terminalRendererSize.width != 0 && this.terminalRendererSize.height != 0)
// termContainer requires scaled sizes.
this.termContainer.TerminalControlSize = new Size()
{ {
var dpiScale = VisualTreeHelper.GetDpi(this); Width = (sizeInfo.NewSize.Width - this.scrollbar.ActualWidth) * dpiScale.DpiScaleX,
Height = sizeInfo.NewSize.Height * dpiScale.DpiScaleY,
};
double width = ((sizeInfo.NewSize.Width * dpiScale.DpiScaleX) - this.terminalRendererSize.width) / dpiScale.DpiScaleX; if (!this.AutoResize)
double height = ((sizeInfo.NewSize.Height * dpiScale.DpiScaleY) - this.terminalRendererSize.height) / dpiScale.DpiScaleY; {
// Renderer will not resize on control resize. We have to manually calculate the margin to fill in the space.
this.terminalGrid.Margin = this.CalculateMargins(sizeInfo.NewSize);
// Prevent negative margin size. // Margins stop resize events, therefore we have to manually check if more space is available and raise
width = width < 0 ? 0 : width; // a resize event if needed.
height = height < 0 ? 0 : height; this.termContainer.RaiseResizedIfDrawSpaceIncreased();
this.terminalGrid.Margin = new Thickness(0, 0, width, height);
} }
base.OnRenderSizeChanged(sizeInfo); base.OnRenderSizeChanged(sizeInfo);
} }
/// <summary>
/// Calculates the margins that should surround the terminal renderer, if any.
/// </summary>
/// <param name="controlSize">New size of the control. Uses the control's current size if not provided.</param>
/// <returns>The new terminal control margin thickness in device independent units.</returns>
private Thickness CalculateMargins(Size controlSize = default)
{
var dpiScale = VisualTreeHelper.GetDpi(this);
double width = 0, height = 0;
if (controlSize == default)
{
controlSize = new Size()
{
Width = this.terminalUserControl.ActualWidth,
Height = this.terminalUserControl.ActualHeight,
};
}
// During initialization, the terminal renderer size will be 0 and the terminal renderer
// draws on all available space. Therefore no margins are needed until resized.
if (this.TerminalRendererSize.Width != 0)
{
width = controlSize.Width - (this.TerminalRendererSize.Width / dpiScale.DpiScaleX);
}
if (this.TerminalRendererSize.Height != 0)
{
height = controlSize.Height - (this.TerminalRendererSize.Height / dpiScale.DpiScaleX);
}
width -= this.scrollbar.ActualWidth;
// Prevent negative margin size.
width = width < 0 ? 0 : width;
height = height < 0 ? 0 : height;
return new Thickness(0, 0, width, height);
}
private void TerminalControl_GotFocus(object sender, RoutedEventArgs e) private void TerminalControl_GotFocus(object sender, RoutedEventArgs e)
{ {
e.Handled = true; e.Handled = true;