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
vcruntime
visualstudio
VSTHRD
wlk
wslpath
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
/// on user action.
/// </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>
/// Gets the current character rows available to the terminal.
@ -78,18 +89,6 @@ namespace Microsoft.Terminal.Wpf
/// </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>
@ -139,7 +138,7 @@ namespace Microsoft.Terminal.Wpf
NativeMethods.TerminalSetTheme(this.terminal, theme, fontFamily, fontSize, (int)dpiScale.PixelsPerInchX);
this.TriggerResize(this.RenderSize);
this.Resize(this.TerminalControlSize);
}
/// <summary>
@ -157,26 +156,22 @@ namespace Microsoft.Terminal.Wpf
}
/// <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>
/// <param name="renderSize">Size of the rendering window.</param>
/// <returns>Tuple with rows and columns.</returns>
internal (int rows, int columns) TriggerResize(Size renderSize)
internal void Resize(Size renderSize)
{
var dpiScale = VisualTreeHelper.GetDpi(this);
NativeMethods.COORD dimensions;
NativeMethods.TerminalTriggerResize(
this.terminal,
Convert.ToInt16(renderSize.Width * dpiScale.DpiScaleX),
Convert.ToInt16(renderSize.Height * dpiScale.DpiScaleY),
out dimensions);
Convert.ToInt16(renderSize.Width),
Convert.ToInt16(renderSize.Height),
out NativeMethods.COORD dimensions);
this.Rows = dimensions.Y;
this.Columns = dimensions.X;
this.TerminalRendererSize = renderSize;
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);
return (dimensions.Y, dimensions.X);
}
/// <summary>
@ -184,8 +179,7 @@ namespace Microsoft.Terminal.Wpf
/// </summary>
/// <param name="rows">Number of rows 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 (int width, int height) Resize(uint rows, uint columns)
internal void Resize(uint rows, uint columns)
{
NativeMethods.SIZE dimensionsInPixels;
NativeMethods.COORD dimensions = new NativeMethods.COORD
@ -196,20 +190,41 @@ namespace Microsoft.Terminal.Wpf
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.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/>
@ -335,21 +350,23 @@ namespace Microsoft.Terminal.Wpf
NativeMethods.COORD dimensions;
// We only trigger a resize if we want to fill to maximum size.
if (this.AutoFill)
if (this.AutoResize)
{
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;
this.TerminalRendererSize = new Size()
{
Width = windowpos.cx,
Height = windowpos.cy,
};
}
else
{
NativeMethods.TerminalCalculateResize(this.terminal, (short)windowpos.cx, (short)windowpos.cy, out dimensions);
this.MaxColumns = dimensions.X;
this.MaxRows = dimensions.Y;
// Calculate the new columns and rows that fit the total control size and alert the control to redraw the margins.
NativeMethods.TerminalCalculateResize(this.terminal, (short)this.TerminalControlSize.Width, (short)this.TerminalControlSize.Height, out dimensions);
}
this.Connection?.Resize((uint)dimensions.Y, (uint)dimensions.X);

View file

@ -6,10 +6,12 @@
namespace Microsoft.Terminal.Wpf
{
using System;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Task = System.Threading.Tasks.Task;
/// <summary>
/// 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 width, int height) terminalRendererSize;
/// <summary>
/// Gets size of the terminal renderer.
/// </summary>
private Size TerminalRendererSize
{
get => this.termContainer.TerminalRendererSize;
}
/// <summary>
/// Initializes a new instance of the <see cref="TerminalControl"/> class.
@ -44,24 +52,14 @@ namespace Microsoft.Terminal.Wpf
/// </summary>
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>
/// Gets or sets a value indicating whether if the renderer should automatically resize to fill the control
/// on user action.
/// </summary>
public bool AutoFill
public bool AutoResize
{
get => this.termContainer.AutoFill;
set => this.termContainer.AutoFill = value;
get => this.termContainer.AutoResize;
set => this.termContainer.AutoResize = value;
}
/// <summary>
@ -69,10 +67,7 @@ namespace Microsoft.Terminal.Wpf
/// </summary>
public ITerminalConnection Connection
{
set
{
this.termContainer.Connection = value;
}
set => this.termContainer.Connection = value;
}
/// <summary>
@ -114,56 +109,100 @@ namespace Microsoft.Terminal.Wpf
/// </summary>
/// <param name="rows">Number of rows 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);
double marginWidth = ((this.terminalUserControl.RenderSize.Width * dpiScale.DpiScaleX) - this.terminalRendererSize.width) / dpiScale.DpiScaleX;
double marginHeight = ((this.terminalUserControl.RenderSize.Height * dpiScale.DpiScaleY) - this.terminalRendererSize.height) / dpiScale.DpiScaleY;
// 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);
#pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs
await this.Dispatcher.BeginInvoke(
new Action(delegate() { this.terminalGrid.Margin = this.CalculateMargins(); }),
System.Windows.Threading.DispatcherPriority.Render);
#pragma warning restore VSTHRD001 // Avoid legacy thread switching APIs
}
/// <summary>
/// Resizes the terminal to the specified dimensions.
/// </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>
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/>
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.
if (this.AutoFill == false && this.terminalRendererSize.width != 0 && this.terminalRendererSize.height != 0)
var dpiScale = VisualTreeHelper.GetDpi(this);
// 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;
double height = ((sizeInfo.NewSize.Height * dpiScale.DpiScaleY) - this.terminalRendererSize.height) / dpiScale.DpiScaleY;
if (!this.AutoResize)
{
// 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.
width = width < 0 ? 0 : width;
height = height < 0 ? 0 : height;
this.terminalGrid.Margin = new Thickness(0, 0, width, height);
// Margins stop resize events, therefore we have to manually check if more space is available and raise
// a resize event if needed.
this.termContainer.RaiseResizedIfDrawSpaceIncreased();
}
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)
{
e.Handled = true;