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;