diff --git a/.github/actions/spell-check/dictionary/microsoft.txt b/.github/actions/spell-check/dictionary/microsoft.txt
index a5d56bf5bc..e803c06f62 100644
--- a/.github/actions/spell-check/dictionary/microsoft.txt
+++ b/.github/actions/spell-check/dictionary/microsoft.txt
@@ -32,6 +32,7 @@ tasklist
tdbuildteamid
vcruntime
visualstudio
+VSTHRD
wlk
wslpath
wtl
diff --git a/src/cascadia/WpfTerminalControl/TerminalContainer.cs b/src/cascadia/WpfTerminalControl/TerminalContainer.cs
index b242faa0f1..67a6bf6a75 100644
--- a/src/cascadia/WpfTerminalControl/TerminalContainer.cs
+++ b/src/cascadia/WpfTerminalControl/TerminalContainer.cs
@@ -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.
///
- public bool AutoFill { get; set; } = true;
+ internal bool AutoResize { get; set; } = true;
+
+ ///
+ /// Gets or sets the size of the parent user control that hosts the terminal hwnd.
+ ///
+ /// Control size is in device independent units, but for simplicity all sizes should be scaled.
+ internal Size TerminalControlSize { get; set; }
+
+ ///
+ /// Gets or sets the size of the terminal renderer.
+ ///
+ internal Size TerminalRendererSize { get; set; }
///
/// Gets the current character rows available to the terminal.
@@ -78,18 +89,6 @@ namespace Microsoft.Terminal.Wpf
///
internal int Columns { get; private set; }
- ///
- /// Gets the maximum amount of character rows that can fit in this control.
- ///
- /// This will be in sync with unless is set to false.
- internal int MaxRows { get; private set; }
-
- ///
- /// Gets the maximum amount of character columns that can fit in this control.
- ///
- /// This will be in sync with unless is set to false.
- internal int MaxColumns { get; private set; }
-
///
/// Gets the window handle of the terminal.
///
@@ -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);
}
///
@@ -157,26 +156,22 @@ namespace Microsoft.Terminal.Wpf
}
///
- /// Triggers a refresh of the terminal with the given size.
+ /// Triggers a resize of the terminal with the given size, redrawing the rendered text.
///
/// Size of the rendering window.
- /// Tuple with rows and columns.
- 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);
}
///
@@ -184,8 +179,7 @@ namespace Microsoft.Terminal.Wpf
///
/// Number of rows to show.
/// Number of columns to show.
- /// pair with the new width and height size in pixels for the renderer.
- 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);
+ }
+
+ ///
+ /// Calculates the rows and columns that would fit in the given size.
+ ///
+ /// DPI scaled size.
+ /// Amount of rows and columns that would fit the given size.
+ 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);
+ }
+
+ ///
+ /// Triggers the terminal resize event if more space is available in the terminal control.
+ ///
+ internal void RaiseResizedIfDrawSpaceIncreased()
+ {
+ (var columns, var rows) = this.CalculateRowsAndColumns(this.TerminalControlSize);
+
+ if (this.Columns < columns || this.Rows < rows)
+ {
+ this.connection?.Resize(rows, columns);
+ }
}
///
@@ -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);
diff --git a/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs b/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs
index 8862f1dcf1..62ec5cc449 100644
--- a/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs
+++ b/src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs
@@ -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;
///
/// 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;
+ ///
+ /// Gets size of the terminal renderer.
+ ///
+ private Size TerminalRendererSize
+ {
+ get => this.termContainer.TerminalRendererSize;
+ }
///
/// Initializes a new instance of the class.
@@ -44,24 +52,14 @@ namespace Microsoft.Terminal.Wpf
///
public int Columns => this.termContainer.Columns;
- ///
- /// Gets the maximum amount of character rows that can fit in this control.
- ///
- public int MaxRows => this.termContainer.MaxRows;
-
- ///
- /// Gets the maximum amount of character columns that can fit in this control.
- ///
- public int MaxColumns => this.termContainer.MaxColumns;
-
///
/// Gets or sets a value indicating whether if the renderer should automatically resize to fill the control
/// on user action.
///
- public bool AutoFill
+ public bool AutoResize
{
- get => this.termContainer.AutoFill;
- set => this.termContainer.AutoFill = value;
+ get => this.termContainer.AutoResize;
+ set => this.termContainer.AutoResize = value;
}
///
@@ -69,10 +67,7 @@ namespace Microsoft.Terminal.Wpf
///
public ITerminalConnection Connection
{
- set
- {
- this.termContainer.Connection = value;
- }
+ set => this.termContainer.Connection = value;
}
///
@@ -114,56 +109,100 @@ namespace Microsoft.Terminal.Wpf
///
/// Number of rows to display.
/// Number of columns to display.
- public void Resize(uint rows, uint columns)
+ /// Cancellation token for this task.
+ /// A representing the asynchronous operation.
+ 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
}
///
/// Resizes the terminal to the specified dimensions.
///
- /// Rendering size for the terminal.
+ /// Rendering size for the terminal in device independent units.
/// A tuple of (int, int) representing the number of rows and columns in the terminal.
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);
}
///
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);
}
+ ///
+ /// Calculates the margins that should surround the terminal renderer, if any.
+ ///
+ /// New size of the control. Uses the control's current size if not provided.
+ /// The new terminal control margin thickness in device independent units.
+ 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;