wpf: fix margin calculations and resize events (#7892)
This commit is contained in:
parent
cb96aa718f
commit
d2d462fc48
|
@ -32,6 +32,7 @@ tasklist
|
|||
tdbuildteamid
|
||||
vcruntime
|
||||
visualstudio
|
||||
VSTHRD
|
||||
wlk
|
||||
wslpath
|
||||
wtl
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue