diff --git a/Examples/UICatalog/Scenarios/Arrangement.cs b/Examples/UICatalog/Scenarios/Arrangement.cs index 05b582e677..a24a501169 100644 --- a/Examples/UICatalog/Scenarios/Arrangement.cs +++ b/Examples/UICatalog/Scenarios/Arrangement.cs @@ -148,13 +148,10 @@ public override void Main () ShadowStyle = ShadowStyles.Transparent, BorderStyle = LineStyle.Double, TabStop = TabBehavior.TabGroup, - Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped + Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped, + SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Error) }; - datePicker.SetScheme (new Scheme (new Attribute (SchemeManager.GetScheme (Schemes.Accent).Normal.Foreground.GetBrighterColor (), - SchemeManager.GetScheme (Schemes.Accent).Normal.Background.GetBrighterColor (), - SchemeManager.GetScheme (Schemes.Accent).Normal.Style))); - TransparentView transparentView = new () { Title = "Transparent", diff --git a/Examples/UICatalog/Scenarios/PopoverMenus.cs b/Examples/UICatalog/Scenarios/PopoverMenus.cs index 9ee48d0968..69bbd5d17c 100644 --- a/Examples/UICatalog/Scenarios/PopoverMenus.cs +++ b/Examples/UICatalog/Scenarios/PopoverMenus.cs @@ -400,8 +400,7 @@ void CreateAction (List cultures, MenuItem culture) => foreach (MenuItem item in cultures) { - ((CheckBox)item.CommandView).Value = - Thread.CurrentThread.CurrentUICulture.Name == item.HelpText ? CheckState.Checked : CheckState.UnChecked; + (item.CommandView as CheckBox)?.Value = Thread.CurrentThread.CurrentUICulture.Name == item.HelpText ? CheckState.Checked : CheckState.UnChecked; } }; } diff --git a/Examples/UICatalog/Scenarios/Shortcuts.cs b/Examples/UICatalog/Scenarios/Shortcuts.cs index c9b19fc00e..5c3c90aafa 100644 --- a/Examples/UICatalog/Scenarios/Shortcuts.cs +++ b/Examples/UICatalog/Scenarios/Shortcuts.cs @@ -263,7 +263,7 @@ private void HandleOnIsRunningChanged (object? sender, EventArgs e) Command = Command.New }; - _window.CommandNotBound += (o, args) => + _window.CommandNotBound += (_, args) => { if (args.Context?.Command != Command.New) { @@ -329,13 +329,10 @@ private void HandleOnIsRunningChanged (object? sender, EventArgs e) //framedShortcut.Orientation = Orientation.Horizontal; - if (framedShortcut.Padding is { }) - { - framedShortcut.Padding.Thickness = new Thickness (0, 1, 0, 0); - framedShortcut.Padding.Diagnostics = ViewDiagnosticFlags.Ruler; - } + framedShortcut.Padding.Thickness = new Thickness (0, 1, 0, 0); + framedShortcut.Padding.Diagnostics = ViewDiagnosticFlags.Ruler; - if (framedShortcut.CommandView.Margin is { }) + if (framedShortcut.CommandView?.Margin is { }) { framedShortcut.CommandView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Dialog); framedShortcut.HelpView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Error); @@ -537,7 +534,7 @@ void SetCommandViewsCanFocus (bool canFocus) { if (peer.CanFocus) { - peer.CommandView.CanFocus = canFocus; + peer.CommandView?.CanFocus = canFocus; } } focused?.SetFocus (); diff --git a/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs b/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs index d43bc58ddc..61e77b12b7 100644 --- a/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/Examples/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -38,25 +38,27 @@ public override void Main () using IApplication app = Application.Create (); app.Init (); - using Window win = new (); + using Runnable win = new (); win.Title = GetName (); - win.Y = 1; // menu - win.Height = Dim.Fill (); // MenuBar MenuBar menu = new (); - _treeViewFiles = new TreeView { X = 0, Y = Pos.Bottom (menu), Width = Dim.Percent (50), Height = Dim.Fill () }; + _treeViewFiles = new TreeView + { + Y = Pos.Bottom (menu), + Height = Dim.Fill (), + }; _treeViewFiles.DrawLine += TreeViewFiles_DrawLine; - // Scrollbars are disabled by default (VisibilityMode.Manual) - _detailsFrame = new DetailsFrame (_iconProvider) { - X = Pos.Right (_treeViewFiles), Y = Pos.Top (_treeViewFiles), Width = Dim.Fill (), Height = Dim.Fill () + X = Pos.AnchorEnd (), Y = Pos.Bottom (menu), Width = 50, Height = Dim.Fill () }; + _treeViewFiles.Width = Dim.Fill (_detailsFrame); + + win.Add (menu, _treeViewFiles, _detailsFrame); - win.Add (_detailsFrame); _treeViewFiles.Activating += TreeViewFiles_Activating; _treeViewFiles.KeyDown += TreeViewFiles_KeyPress; _treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged; @@ -70,8 +72,6 @@ public override void Main () _miMultiSelectCheckBox = new CheckBox { Title = "_Multi Select" - - //CheckedState = CheckState.Checked }; _miMultiSelectCheckBox.ValueChanged += (_, _) => SetMultiSelect (); @@ -140,10 +140,9 @@ public override void Main () ])); SetNerdIcons (); - win.Add (menu, _treeViewFiles); + _treeViewFiles.GoToFirst (); _treeViewFiles.Expand (); - _treeViewFiles.SetFocus (); UpdateIconCheckState (); @@ -205,20 +204,20 @@ private void SetCustomColors () if (_miCustomColorsCheckBox.Value == CheckState.Checked) { _treeViewFiles.ColorGetter = m => - { - if ((m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) || (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden))) - { - return new Scheme - { - Focus = new Attribute (Color.BrightRed, - _treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background), - Normal = new Attribute (Color.BrightYellow, - _treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background) - }; - } - - return null; - }; + { + if ((m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) || (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden))) + { + return new Scheme + { + Focus = new Attribute (Color.BrightRed, + _treeViewFiles.GetAttributeForRole (VisualRole.Focus).Background), + Normal = new Attribute (Color.BrightYellow, + _treeViewFiles.GetAttributeForRole (VisualRole.Normal).Background) + }; + } + + return null; + }; } else { @@ -393,13 +392,13 @@ private void TreeViewFiles_DrawLine (object? sender, DrawTreeViewLineEventArgs 0 ? us.Viewport.Width : width; - width = us.TextFormatter.FormatAndGetSize (new Size (constrainWidth, screenX4)).Width; + width = us.TextFormatter.FormatAndGetSize (new Size (constrainWidth, int.MaxValue)).Width; } - textSize = us.TextFormatter.FormatAndGetSize (new Size (us.TextFormatter.ConstrainToWidth ?? width, screenX4)).Height; + textSize = us.TextFormatter.FormatAndGetSize (new Size (us.TextFormatter.ConstrainToWidth ?? width, int.MaxValue)).Height; us.TextFormatter.ConstrainToHeight = textSize; } else @@ -248,8 +247,8 @@ internal override int Calculate (int location, int superviewContentSize, View us foreach (View v in categories.Centered) { maxCentered = dimension == Dimension.Width - ? v.X.GetAnchor (0) + v.Width.Calculate (0, screenX4, v, dimension) - : v.Y.GetAnchor (0) + v.Height.Calculate (0, screenX4, v, dimension); + ? v.X.GetAnchor (0) + v.Width.Calculate (0, int.MaxValue, v, dimension) + : v.Y.GetAnchor (0) + v.Height.Calculate (0, int.MaxValue, v, dimension); } maxCalculatedSize = int.Max (maxCalculatedSize, maxCentered); @@ -272,8 +271,8 @@ internal override int Calculate (int location, int superviewContentSize, View us { // Need to set the relative layout for PosAnchorEnd subviews to calculate the size anchoredSubView.SetRelativeLayout (dimension == Dimension.Width - ? new Size (maxCalculatedSize, screenX4) - : new Size (screenX4, maxCalculatedSize)); + ? new Size (maxCalculatedSize, int.MaxValue) + : new Size (int.MaxValue, maxCalculatedSize)); maxAnchorEnd = dimension == Dimension.Width ? anchoredSubView.X.GetAnchor (maxCalculatedSize + anchoredSubView.Frame.Width) diff --git a/Terminal.Gui/ViewBase/View.Content.cs b/Terminal.Gui/ViewBase/View.Content.cs index c51fbf34d9..29ef240d24 100644 --- a/Terminal.Gui/ViewBase/View.Content.cs +++ b/Terminal.Gui/ViewBase/View.Content.cs @@ -523,9 +523,12 @@ private void SetViewport (Rectangle viewport) Size newSize = new (viewport.Size.Width + thickness.Horizontal, viewport.Size.Height + thickness.Vertical); - if (newSize == Frame.Size) + // Detect the "adornments consume entire viewport" no-op: when the requested viewport size equals + // the current Viewport size (which may already be clamped to zero when Frame is smaller than adornments). + // In this case updating Frame would incorrectly grow it, so treat it as a no-op. + if (newSize == Frame.Size || viewport.Size == Viewport.Size) { - // The change is not changing the Frame, so we don't need to update it. + // The change is not changing the Frame, or the adornments consume the entire Frame, so we don't need to update it. // Just call SetNeedsLayout to update the layout. if (_viewportLocation != viewport.Location) { diff --git a/Terminal.Gui/ViewBase/View.Drawing.Scheme.cs b/Terminal.Gui/ViewBase/View.Drawing.Scheme.cs index b80755663d..e61228f768 100644 --- a/Terminal.Gui/ViewBase/View.Drawing.Scheme.cs +++ b/Terminal.Gui/ViewBase/View.Drawing.Scheme.cs @@ -147,6 +147,19 @@ Scheme DefaultAction () if (SchemeManager.TryGetScheme (SchemeName, out Scheme? namedScheme)) { + if (SchemeName != "Accent") + { + return namedScheme; + } + + // If the "Accent" scheme is requested but the theme's definition has no normal background color, + // derive it from the base scheme to ensure it has a valid background. + // There may be cases where a Theme defines an Accent like this; that should be a rare case. + if (namedScheme.Normal.Background == Color.None) + { + return SchemeManager.TryGetScheme ("Base", out Scheme? baseScheme) ? Scheme.DeriveAccent (baseScheme, Driver?.DefaultAttribute) : namedScheme; + } + return namedScheme; } diff --git a/Terminal.Gui/ViewBase/View.ScrollBars.cs b/Terminal.Gui/ViewBase/View.ScrollBars.cs index da8c64b496..3fc55e4bfe 100644 --- a/Terminal.Gui/ViewBase/View.ScrollBars.cs +++ b/Terminal.Gui/ViewBase/View.ScrollBars.cs @@ -144,6 +144,21 @@ private void ConfigureVerticalScrollBarEvents (ScrollBar scrollBar) Viewport = Viewport with { Y = Math.Min (args.NewValue, scrollBar.ScrollableContentSize - scrollBar.VisibleContentSize) }; }; + scrollBar.VisibleChanging += (_, args) => + { + if (!args.NewValue) + { + return; + } + int width = Frame.Size.Width - GetAdornmentsThickness ().Horizontal; + + if (width < 1 || Viewport.Height < 2) + { + // Prevent scrollbar from becoming visible if it would cause negative available space for content + args.Cancel = true; + } + }; + scrollBar.VisibleChanged += (_, _) => { // Reset scrolling @@ -166,6 +181,21 @@ private void ConfigureHorizontalScrollBarEvents (ScrollBar scrollBar) Viewport = Viewport with { X = Math.Min (args.NewValue, scrollBar.ScrollableContentSize - scrollBar.VisibleContentSize) }; }; + scrollBar.VisibleChanging += (_, args) => + { + if (!args.NewValue) + { + return; + } + int height = Frame.Size.Height - GetAdornmentsThickness ().Vertical; + + if (Viewport.Width < 2 || height < 1) + { + // Prevent scrollbar from becoming visible if it would cause negative available space for content + args.Cancel = true; + } + }; + scrollBar.VisibleChanged += (_, _) => { // Reset scrolling diff --git a/Terminal.Gui/Views/DropDownList.cs b/Terminal.Gui/Views/DropDownList.cs index ceef0f4074..a9f198ff88 100644 --- a/Terminal.Gui/Views/DropDownList.cs +++ b/Terminal.Gui/Views/DropDownList.cs @@ -200,6 +200,19 @@ public override void EndInit () base.EndInit (); } + /// + protected override bool OnMouseEvent (Mouse ev) + { + if (!ReadOnly || !ev.Flags.FastHasFlags (MouseFlags.LeftButtonPressed)) + { + return base.OnMouseEvent (ev); + } + + App?.Popovers?.Register (_listPopover); + + return InvokeCommand (Command.Activate) is true; + } + /// protected override bool OnHasFocusChanging (bool currentHasFocus, bool newHasFocus, View? currentFocused, View? newFocused) { @@ -304,18 +317,18 @@ protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attri case VisualRole.ReadOnly when ReadOnly: case VisualRole.Active when ReadOnly: - { - currentAttribute = GetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Normal); + { + currentAttribute = GetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Normal); - return true; - } + return true; + } case VisualRole.Editable when ReadOnly: - { - currentAttribute = GetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Normal); + { + currentAttribute = GetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Normal); - break; - } + break; + } } return false; diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index ac3628dd24..04a88bb9e0 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -309,7 +309,7 @@ public bool EnableForDesign (ref TContext targetView) where TContext : ]) { Id = "Preferences" } }, new Line (), - new MenuItem { TargetView = targetView as View, Key = Application.GetDefaultKey (Command.Quit), Command = Command.Quit } + new MenuItem { TargetView = targetView as View, Command = Command.Quit } ])); Add (new MenuBarItem ("_Edit", diff --git a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs index e6474dfe1f..0cf86232a1 100644 --- a/Terminal.Gui/Views/ScrollBar/ScrollBar.cs +++ b/Terminal.Gui/Views/ScrollBar/ScrollBar.cs @@ -90,6 +90,14 @@ private void ShowHide () switch (VisibilityMode) { case ScrollBarVisibilityMode.Auto: + if (VisibleContentSize < 2) + { + // Not enough room to show both buttons, so hide the scrollbar + Visible = false; + + break; + } + // VisibilityMode is the authority. ViewportSettings flags are a // convenience that *sets* VisibilityMode via SyncOneScrollBar; they // should not be re-checked here. When the flag is later removed, diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 0264cb8311..7f29fd8660 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -129,6 +129,7 @@ public Shortcut (Key key, string? commandText, Action? action, string? helpText #if DEBUG Id = "CommandView", #endif + Frame = new (0, 0, 2, 1), Width = Dim.Auto (), Height = Dim.Fill () }; @@ -208,30 +209,30 @@ protected override void OnSubViewLayout (LayoutEventArgs e) ShowHide (); ForceCalculateNaturalWidth (); - if (Width.Has (out _) || HelpView.Margin is null) + if (Width.Has (out _)) { return; } // Frame.Width is smaller than the natural width. Reduce width of HelpView. - _maxHelpWidth = int.Max (0, GetContentWidth () - CommandView.Frame.Width - KeyView.Frame.Width); + _maxHelpWidth = int.Max (0, GetContentWidth () - (CommandView?.Frame.Width ?? 0) - KeyView.Frame.Width); if (_maxHelpWidth < 3) { Thickness t = GetMarginThickness (); HelpView.Margin.Thickness = _maxHelpWidth switch - { - 0 or 1 => + { + 0 or 1 => - // Scrunch it by removing both margins - new Thickness (t.Right - 1, t.Top, t.Left - 1, t.Bottom), - 2 => + // Scrunch it by removing both margins + new Thickness (t.Right - 1, t.Top, t.Left - 1, t.Bottom), + 2 => - // Scrunch just the right margin - new Thickness (t.Right, t.Top, t.Left - 1, t.Bottom), - _ => HelpView.Margin.Thickness - }; + // Scrunch just the right margin + new Thickness (t.Right, t.Top, t.Left - 1, t.Bottom), + _ => HelpView.Margin.Thickness + }; } else { @@ -251,7 +252,7 @@ protected override void OnSubViewLayout (LayoutEventArgs e) // so Pos.Align works correctly. internal void ShowHide () { - if (CommandView.Visible) + if (CommandView?.Visible == true) { if (CommandView.SuperView is null) { @@ -261,7 +262,7 @@ internal void ShowHide () } else { - if (CommandView.SuperView is { }) + if (CommandView?.SuperView is { }) { Remove (CommandView); } @@ -301,7 +302,11 @@ internal void ShowHide () MoveSubViewToStart (KeyView); MoveSubViewToStart (HelpView); - MoveSubViewToStart (CommandView); + + if (CommandView is { }) + { + MoveSubViewToStart (CommandView); + } } // Force Width to DimAuto to calculate natural width and then set it back @@ -309,7 +314,7 @@ private void ForceCalculateNaturalWidth () { // Get the natural size of each subview Size screenSize = App?.Screen.Size ?? new Size (2048, 2048); - CommandView.SetRelativeLayout (screenSize); + CommandView?.SetRelativeLayout (screenSize); HelpView.SetRelativeLayout (screenSize); KeyView.SetRelativeLayout (screenSize); @@ -331,7 +336,7 @@ private void ForceCalculateNaturalWidth () /// Programmatic guard (skip if no binding) /// /// - protected override View GetDispatchTarget (ICommandContext? ctx) => CommandView; + protected override View? GetDispatchTarget (ICommandContext? ctx) => CommandView; // ConsumeDispatch defaults to false — CommandView completes its own activation // (e.g., CheckBox.OnActivated calls AdvanceCheckState). @@ -432,7 +437,7 @@ protected override bool OnAccepting (CommandEventArgs args) #region Command - private View _commandView = new (); + private View? _commandView; /// /// Gets or sets the View that displays the command text and hotkey. @@ -476,31 +481,29 @@ protected override bool OnAccepting (CommandEventArgs args) /// StatusBar.Add(force16ColorsShortcut); /// /// - public View CommandView + public View? CommandView { get => _commandView; set { - ArgumentNullException.ThrowIfNull (value); - // Clean up old - _commandView.GettingAttributeForRole -= SubViewOnGettingAttributeForRole; + _commandView?.GettingAttributeForRole -= SubViewOnGettingAttributeForRole; Remove (_commandView); - _commandView.Dispose (); + _commandView?.Dispose (); // Set new _commandView = value; #if DEBUG - if (string.IsNullOrEmpty (_commandView.Id)) + if (string.IsNullOrEmpty (_commandView?.Id)) { - _commandView.Id = "_commandView"; + _commandView?.Id = "_commandView"; } #endif - _commandView.GettingAttributeForRole += SubViewOnGettingAttributeForRole; + _commandView?.GettingAttributeForRole += SubViewOnGettingAttributeForRole; // If the CommandView has a hotkey, we use that. Otherwise, we use '_' to indicate the hotkey is in the Title. - if (_commandView.HotKey != Key.Empty) + if (_commandView?.HotKey != Key.Empty) { HotKeySpecifier = (Rune)'\xffff'; } @@ -508,7 +511,7 @@ public View CommandView { HotKeySpecifier = (Rune)'_'; } - Title = _commandView.Text; + Title = _commandView?.Text ?? string.Empty; UpdateKeyBindings (Key.Empty); UpdateMouseBindings (); @@ -527,7 +530,7 @@ private void UpdateMouseBindings () MouseBindings.Clear (); - foreach (KeyValuePair mb in CommandView.MouseBindings.GetBindings ()) + foreach (KeyValuePair mb in CommandView?.MouseBindings.GetBindings () ?? []) { MouseBindings.Add (mb.Key, mb.Value); } @@ -535,22 +538,19 @@ private void UpdateMouseBindings () private void SetCommandViewDefaultLayout () { - if (CommandView.Margin is { }) - { - CommandView.Margin.Thickness = GetMarginThickness (); + CommandView?.Margin.Thickness = GetMarginThickness (); - // Margin must be transparent to mouse, so clicks pass through to Shortcut - CommandView.Margin.ViewportSettings |= ViewportSettingsFlags.TransparentMouse; - } + // Margin must be transparent to mouse, so clicks pass through to Shortcut + CommandView?.Margin.ViewportSettings |= ViewportSettingsFlags.TransparentMouse; - CommandView.X = Pos.Align (Alignment.End, AlignmentModes); + CommandView?.X = Pos.Align (Alignment.End, AlignmentModes); - CommandView.VerticalTextAlignment = Alignment.Center; - CommandView.TextAlignment = Alignment.Start; - CommandView.TextFormatter.WordWrap = false; + CommandView?.VerticalTextAlignment = Alignment.Center; + CommandView?.TextAlignment = Alignment.Start; + CommandView?.TextFormatter.WordWrap = false; - CommandView.MouseHighlightStates = MouseState.None; - CommandView.GettingAttributeForRole += SubViewOnGettingAttributeForRole; + CommandView?.MouseHighlightStates = MouseState.None; + CommandView?.GettingAttributeForRole += SubViewOnGettingAttributeForRole; } private void SubViewOnGettingAttributeForRole (object? sender, VisualRoleEventArgs e) @@ -607,7 +607,7 @@ private void Shortcut_TitleChanged (object? sender, EventArgs e) => // If the Title changes, update the CommandView Text. // This is a helper to make it easier to set the CommandView text. // CommandView is public and replaceable, but this is a convenience. - _commandView.Text = Title; + _commandView?.Text = Title; /// /// Gets or sets the target that the will be invoked on @@ -654,17 +654,14 @@ public Command Command /// /// The subview that displays the help text for the command. Internal for unit testing. /// - public View HelpView { get; } = new () { /*ViewportSettings = ViewportSettingsFlags.TransparentMouse*/ }; + public View HelpView { get; } = new () { Frame = new Rectangle (0, 0, 2, 1) }; private void SetHelpViewDefaultLayout () { - if (HelpView.Margin is { }) - { - HelpView.Margin.Thickness = GetMarginThickness (); + HelpView.Margin.Thickness = GetMarginThickness (); - // Margin must be transparent to mouse, so clicks pass through to Shortcut - HelpView.Margin.ViewportSettings |= ViewportSettingsFlags.TransparentMouse; - } + // Margin must be transparent to mouse, so clicks pass through to Shortcut + HelpView.Margin.ViewportSettings |= ViewportSettingsFlags.TransparentMouse; HelpView.X = Pos.Align (Alignment.End, AlignmentModes); _maxHelpWidth = HelpView.Text.GetColumns (); @@ -760,7 +757,7 @@ public bool BindKeyToApplication /// Gets the subview that displays the key. Is drawn with Normal and HotNormal colors reversed. /// - public View KeyView { get; } = new () { /*ViewportSettings = ViewportSettingsFlags.TransparentMouse*/ }; + public View KeyView { get; } = new () { Frame = new Rectangle (0, 0, 2, 1) }; /// /// Gets or sets the minimum size of the key text. Useful for aligning the key text with other s. @@ -782,13 +779,10 @@ public int MinimumKeyTextSize private void SetKeyViewDefaultLayout () { - if (KeyView.Margin is { }) - { - KeyView.Margin.Thickness = GetMarginThickness (); + KeyView.Margin.Thickness = GetMarginThickness (); - // Margin must be transparent to mouse, so clicks pass through to Shortcut - KeyView.Margin.ViewportSettings |= ViewportSettingsFlags.TransparentMouse; - } + // Margin must be transparent to mouse, so clicks pass through to Shortcut + KeyView.Margin.ViewportSettings |= ViewportSettingsFlags.TransparentMouse; KeyView.X = Pos.Align (Alignment.End, AlignmentModes); KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (_ => MinimumKeyTextSize)); @@ -883,9 +877,10 @@ protected override void Dispose (bool disposing) { TitleChanged -= Shortcut_TitleChanged; - if (CommandView.SuperView is null) + if (_commandView?.SuperView is null) { - CommandView.Dispose (); + _commandView?.Dispose (); + _commandView = null; } if (HelpView.SuperView is null) diff --git a/Tests/UnitTestsParallelizable/ViewBase/Layout/ViewportTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Layout/ViewportTests.cs index 2887535487..8707cafb01 100644 --- a/Tests/UnitTestsParallelizable/ViewBase/Layout/ViewportTests.cs +++ b/Tests/UnitTestsParallelizable/ViewBase/Layout/ViewportTests.cs @@ -227,7 +227,7 @@ public void Set_Viewport_ValueGreaterThanContentSize_UpdatesViewportToContentSiz { // Arrange var view = new View (); - view.SetContentSize (new (100, 100)); + view.SetContentSize (new Size (100, 100)); var newViewport = new Rectangle (0, 0, 200, 200); view.ViewportSettings = ViewportSettingsFlags.AllowLocationGreaterThanContentSize; @@ -276,7 +276,7 @@ public void GetViewportOffset_Returns_Offset_From_Frame (int adornmentThickness, View view = new () { X = 1, Y = 1, Width = 10, Height = 10 }; view.BeginInit (); view.EndInit (); - view.Margin.Thickness = new (adornmentThickness); + view.Margin.Thickness = new Thickness (adornmentThickness); Assert.Equal (expectedOffset, view.GetViewportOffsetFromFrame ().X); } @@ -303,7 +303,7 @@ public void ContentSize_Tracks_ViewportSize_If_ContentSizeTracksViewport_Is_True { View view = new () { Width = 1, Height = 1 }; view.SetContentSize (new Size (5, 5)); - view.Viewport = new (0, 0, 10, 10); + view.Viewport = new Rectangle (0, 0, 10, 10); view.ContentSizeTracksViewport = true; Assert.Equal (view.Viewport.Size, view.GetContentSize ()); } @@ -313,7 +313,7 @@ public void ContentSize_Ignores_ViewportSize_If_ContentSizeTracksViewport_Is_Fal { View view = new () { Width = 1, Height = 1 }; view.SetContentSize (new Size (5, 5)); - view.Viewport = new (0, 0, 10, 10); + view.Viewport = new Rectangle (0, 0, 10, 10); view.ContentSizeTracksViewport = false; Assert.NotEqual (view.Viewport.Size, view.GetContentSize ()); } @@ -323,7 +323,7 @@ private class TestViewportEventsView : View public int OnViewportChangedCallCount { get; private set; } public int ViewportChangedEventCallCount { get; private set; } - public TestViewportEventsView () => ViewportChanged += (sender, args) => ViewportChangedEventCallCount++; + public TestViewportEventsView () => ViewportChanged += (_, _) => ViewportChangedEventCallCount++; protected override void OnViewportChanged (DrawEventArgs e) { @@ -409,7 +409,7 @@ public void Set_Viewport_X_ClampsToPreventBlankSpace_ByDefault () { // Arrange - View with content that fits exactly in viewport var view = new View { Width = 10, Height = 10 }; - view.SetContentSize (new (20, 20)); // Content larger than viewport + view.SetContentSize (new Size (20, 20)); // Content larger than viewport view.Layout (); // Act - Try to set viewport X such that X + Width > ContentSize.Width @@ -425,7 +425,7 @@ public void Set_Viewport_Y_ClampsToPreventBlankSpace_ByDefault () { // Arrange - View with content that fits exactly in viewport var view = new View { Width = 10, Height = 10 }; - view.SetContentSize (new (20, 20)); // Content larger than viewport + view.SetContentSize (new Size (20, 20)); // Content larger than viewport view.Layout (); // Act - Try to set viewport Y such that Y + Height > ContentSize.Height @@ -441,7 +441,7 @@ public void Set_Viewport_X_AllowsOverscroll_WhenFlagSet () { // Arrange var view = new View { Width = 10, Height = 10, ViewportSettings = ViewportSettingsFlags.AllowXPlusWidthGreaterThanContentWidth }; - view.SetContentSize (new (20, 20)); + view.SetContentSize (new Size (20, 20)); view.Layout (); // Act - Set viewport X such that X + Width > ContentSize.Width @@ -456,7 +456,7 @@ public void Set_Viewport_Y_AllowsOverscroll_WhenFlagSet () { // Arrange var view = new View { Width = 10, Height = 10, ViewportSettings = ViewportSettingsFlags.AllowYPlusHeightGreaterThanContentHeight }; - view.SetContentSize (new (20, 20)); + view.SetContentSize (new Size (20, 20)); view.Layout (); // Act - Set viewport Y such that Y + Height > ContentSize.Height @@ -471,7 +471,7 @@ public void Set_Viewport_Location_AllowsOverscroll_WhenCombinedFlagSet () { // Arrange var view = new View { Width = 10, Height = 10, ViewportSettings = ViewportSettingsFlags.AllowLocationPlusSizeGreaterThanContentSize }; - view.SetContentSize (new (20, 20)); + view.SetContentSize (new Size (20, 20)); view.Layout (); // Act - Set viewport location such that X + Width and Y + Height exceed content size @@ -491,7 +491,7 @@ public void Set_Viewport_X_ClampsCorrectly_WhenViewportSizeEqualsContentSize () // ContentSize = Viewport.Size = 10x10 - // Act - Try to set positive viewport X + // Act - Try to set positive viewport X // X=1, Width=10, ContentSize.Width=10 -> 1+10=11 > 10, should clamp to X=0 view.Viewport = view.Viewport with { X = 1 }; @@ -525,7 +525,7 @@ public void Set_Viewport_X_ClampsToMaxValidValue (int requestedX, int expectedX) { // Arrange - ContentSize larger than Viewport var view = new View { Width = 10, Height = 10 }; - view.SetContentSize (new (20, 20)); // ContentSize.Width = 20, Viewport.Width = 10 + view.SetContentSize (new Size (20, 20)); // ContentSize.Width = 20, Viewport.Width = 10 view.Layout (); // Act @@ -544,7 +544,7 @@ public void Set_Viewport_Y_ClampsToMaxValidValue (int requestedY, int expectedY) { // Arrange - ContentSize larger than Viewport var view = new View { Width = 10, Height = 10 }; - view.SetContentSize (new (20, 20)); // ContentSize.Height = 20, Viewport.Height = 10 + view.SetContentSize (new Size (20, 20)); // ContentSize.Height = 20, Viewport.Height = 10 view.Layout (); // Act @@ -555,4 +555,139 @@ public void Set_Viewport_Y_ClampsToMaxValidValue (int requestedY, int expectedY) } #endregion + + #region SetViewport should not grow Frame when adornments consume entire Viewport (Issue #5043) + + [Theory] + [InlineData (0, 1, 0)] // Padding right=1 → Horizontal=1, Frame.Width=0, Viewport.Width=0 + [InlineData (1, 1, 0)] // Padding right=1 → Horizontal=1, Frame.Width=1, Viewport.Width=0 + [InlineData (2, 1, 1)] // Padding right=1 → Horizontal=1, Frame.Width=2, Viewport.Width=1 + [InlineData (2, 2, 0)] // Padding right=2 → Horizontal=2, Frame.Width=2, Viewport.Width=0 + [InlineData (3, 2, 1)] // Padding right=2 → Horizontal=2, Frame.Width=3, Viewport.Width=1 + [InlineData (2, 3, 0)] // Padding right=3 → Horizontal=3, Frame.Width=2, Viewport.Width=0 + public void Set_Viewport_DoesNotGrowFrame_WhenPaddingThicknessConsumesViewport (int frameSize, int paddingRight, int expectedViewportWidth) + { + // Arrange - simulate scrollbar adding padding (right/bottom) that consumes the Viewport + var view = new View { Frame = new Rectangle (0, 0, frameSize, frameSize) }; + view.Padding.Thickness = new Thickness (0, 0, paddingRight, paddingRight); + + Rectangle originalFrame = view.Frame; + + // Sanity: verify Viewport is as expected + Assert.Equal (expectedViewportWidth, view.Viewport.Width); + Assert.Equal (expectedViewportWidth, view.Viewport.Height); + + // Act - setting Viewport to its current value should be a no-op + view.Viewport = view.Viewport; + + // Assert - Frame must not have grown + Assert.Equal (originalFrame, view.Frame); + } + + [Theory] + [InlineData (0, 1)] // Border=1 → Horizontal=2, Frame.Width=0, Viewport.Width=0 + [InlineData (1, 1)] // Border=1 → Horizontal=2, Frame.Width=1, Viewport.Width=0 + [InlineData (2, 1)] // Border=1 → Horizontal=2, Frame.Width=2, Viewport.Width=0 + [InlineData (3, 1)] // Border=1 → Horizontal=2, Frame.Width=3, Viewport.Width=1 + [InlineData (4, 2)] // Border=2 → Horizontal=4, Frame.Width=4, Viewport.Width=0 + public void Set_Viewport_DoesNotGrowFrame_WhenBorderThicknessConsumesViewport (int frameSize, int borderThickness) + { + // Arrange + var view = new View { Frame = new Rectangle (0, 0, frameSize, frameSize) }; + view.Border.Thickness = new Thickness (borderThickness); + + Rectangle originalFrame = view.Frame; + int expectedViewportW = Math.Max (0, frameSize - borderThickness * 2); + + // Sanity + Assert.Equal (expectedViewportW, view.Viewport.Width); + + // Act + view.Viewport = view.Viewport; + + // Assert + Assert.Equal (originalFrame, view.Frame); + } + + [Theory] + [InlineData (0, 1)] // Margin=1 → Horizontal=2, Frame.Width=0, Viewport.Width=0 + [InlineData (1, 1)] // Margin=1 → Horizontal=2, Frame.Width=1, Viewport.Width=0 + [InlineData (2, 1)] // Margin=1 → Horizontal=2, Frame.Width=2, Viewport.Width=0 + [InlineData (3, 1)] // Margin=1 → Horizontal=2, Frame.Width=3, Viewport.Width=1 + public void Set_Viewport_DoesNotGrowFrame_WhenMarginThicknessConsumesViewport (int frameSize, int marginThickness) + { + // Arrange + var view = new View { Frame = new Rectangle (0, 0, frameSize, frameSize) }; + view.Margin.Thickness = new Thickness (marginThickness); + + Rectangle originalFrame = view.Frame; + int expectedViewportW = Math.Max (0, frameSize - marginThickness * 2); + + // Sanity + Assert.Equal (expectedViewportW, view.Viewport.Width); + + // Act + view.Viewport = view.Viewport; + + // Assert + Assert.Equal (originalFrame, view.Frame); + } + + [Theory] + [InlineData (4, 1, 1, 1)] // All adornments = 1 → total Horizontal=6 > Frame.Width=4 → Viewport.Width=0 + [InlineData (6, 1, 1, 1)] // All = 1 → Horizontal=6, Frame.Width=6, Viewport.Width=0 + [InlineData (7, 1, 1, 1)] // All = 1 → Horizontal=6, Frame.Width=7, Viewport.Width=1 + [InlineData (3, 1, 0, 1)] // Margin=1, Padding=1 → Horizontal=4 > Frame.Width=3 → Viewport.Width=0 + [InlineData (4, 1, 0, 1)] // Margin=1, Padding=1 → Horizontal=4, Frame.Width=4, Viewport.Width=0 + [InlineData (5, 1, 0, 1)] // Margin=1, Padding=1 → Horizontal=4, Frame.Width=5, Viewport.Width=1 + public void Set_Viewport_DoesNotGrowFrame_WhenCombinedAdornmentsConsumeViewport (int frameSize, + int marginThickness, + int borderThickness, + int paddingThickness) + { + // Arrange + var view = new View { Frame = new Rectangle (0, 0, frameSize, frameSize) }; + view.Margin.Thickness = new Thickness (marginThickness); + view.Border.Thickness = new Thickness (borderThickness); + view.Padding.Thickness = new Thickness (paddingThickness); + + Rectangle originalFrame = view.Frame; + int totalThickness = (marginThickness + borderThickness + paddingThickness) * 2; + int expectedViewportW = Math.Max (0, frameSize - totalThickness); + + // Sanity + Assert.Equal (expectedViewportW, view.Viewport.Width); + + // Act + view.Viewport = view.Viewport; + + // Assert + Assert.Equal (originalFrame, view.Frame); + } + + [Theory] + [InlineData (2, 0, 0, 1, 0)] // Padding bottom=1, Frame.Height=2 → Viewport.Height=1, Viewport.Width=2 (only vertical affected) + [InlineData (2, 0, 0, 0, 1)] // Padding right=1, Frame.Width=2 → Viewport.Width=1, Viewport.Height=2 (only horizontal affected) + [InlineData (1, 0, 0, 1, 0)] // Padding bottom=1, Frame.Height=1 → Viewport.Height=0 (vertical consumed) + [InlineData (1, 0, 0, 0, 1)] // Padding right=1, Frame.Width=1 → Viewport.Width=0 (horizontal consumed) + public void Set_Viewport_DoesNotGrowFrame_WhenAsymmetricPaddingConsumesOneDimension (int frameSize, + int paddingLeft, + int paddingTop, + int paddingBottom, + int paddingRight) + { + // Arrange - simulates scrollbar adding thickness in only one direction + var view = new View { Frame = new Rectangle (0, 0, frameSize, frameSize) }; + view.Padding.Thickness = new Thickness (paddingLeft, paddingTop, paddingRight, paddingBottom); + + Rectangle originalFrame = view.Frame; + + // Act + view.Viewport = view.Viewport; + + // Assert + Assert.Equal (originalFrame, view.Frame); + } + + #endregion } diff --git a/Tests/UnitTestsParallelizable/Views/EnableForDesignEventTests.cs b/Tests/UnitTestsParallelizable/Views/EnableForDesignEventTests.cs index e6e1dd45db..497e424ef3 100644 --- a/Tests/UnitTestsParallelizable/Views/EnableForDesignEventTests.cs +++ b/Tests/UnitTestsParallelizable/Views/EnableForDesignEventTests.cs @@ -77,7 +77,7 @@ public void Bar_EnableForDesign_CheckBox_CommandView_Direct_Activate_Changes_Sta // Find the Shortcut that has a CheckBox CommandView Shortcut checkBoxShortcut = bar.SubViews.OfType () .First (s => s.CommandView is CheckBox); - CheckBox checkBox = (CheckBox)checkBoxShortcut.CommandView; + CheckBox checkBox = (CheckBox)checkBoxShortcut.CommandView!; Assert.Equal (CheckState.UnChecked, checkBox.Value); diff --git a/Tests/UnitTestsParallelizable/Views/ShortcutTests.KeyDown.cs b/Tests/UnitTestsParallelizable/Views/ShortcutTests.KeyDown.cs index 9866371f3a..625bb32447 100644 --- a/Tests/UnitTestsParallelizable/Views/ShortcutTests.KeyDown.cs +++ b/Tests/UnitTestsParallelizable/Views/ShortcutTests.KeyDown.cs @@ -245,7 +245,7 @@ public void KeyDown_Valid_Keys_Raises_Accepted_Activated_Correctly (bool canFocu // The default CommandView does not have a HotKey, so only the Shortcut's Key should trigger activation, not the CommandView's HotKey Assert.Equal (Key.A, shortcut.Key); Assert.Equal (Key.C, shortcut.HotKey); - Assert.Equal (Key.Empty, shortcut.CommandView.HotKey); + Assert.Equal (Key.Empty, shortcut.CommandView?.HotKey); runnable.Add (shortcut); @@ -290,7 +290,7 @@ public void Mouse_Click_On_CommandView_Causes_Activation () // Verify layout created gaps Assert.True (shortcut.Frame.Width >= 40, "Shortcut should be wide enough for gaps"); - Assert.True (shortcut.CommandView.Frame.Width > 0, "CommandView should be visible"); + Assert.True (shortcut.CommandView?.Frame.Width > 0, "CommandView should be visible"); Assert.True (shortcut.HelpView.Frame.Width > 0, "HelpView should be visible"); Assert.True (shortcut.KeyView.Frame.Width > 0, "KeyView should be visible"); @@ -334,7 +334,7 @@ public void Mouse_Click_On_HelpView_Causes_Activation () // Verify layout created gaps Assert.True (shortcut.Frame.Width >= 40, "Shortcut should be wide enough for gaps"); - Assert.True (shortcut.CommandView.Frame.Width > 0, "CommandView should be visible"); + Assert.True (shortcut.CommandView?.Frame.Width > 0, "CommandView should be visible"); Assert.True (shortcut.HelpView.Frame.Width > 0, "HelpView should be visible"); Assert.True (shortcut.KeyView.Frame.Width > 0, "KeyView should be visible"); @@ -378,7 +378,7 @@ public void Mouse_Click_On_KeyView_Causes_Activation () // Verify layout created gaps Assert.True (shortcut.Frame.Width >= 40, "Shortcut should be wide enough for gaps"); - Assert.True (shortcut.CommandView.Frame.Width > 0, "CommandView should be visible"); + Assert.True (shortcut.CommandView?.Frame.Width > 0, "CommandView should be visible"); Assert.True (shortcut.HelpView.Frame.Width > 0, "HelpView should be visible"); Assert.True (shortcut.KeyView.Frame.Width > 0, "KeyView should be visible"); @@ -426,7 +426,7 @@ public void Mouse_Click_Anywhere_On_Shortcut_Causes_Activation () // Verify layout created gaps Assert.True (shortcut.Frame.Width >= 40, "Shortcut should be wide enough for gaps"); - Assert.True (shortcut.CommandView.Frame.Width > 0, "CommandView should be visible"); + Assert.True (shortcut.CommandView?.Frame.Width > 0, "CommandView should be visible"); Assert.True (shortcut.HelpView.Frame.Width > 0, "HelpView should be visible"); Assert.True (shortcut.KeyView.Frame.Width > 0, "KeyView should be visible"); diff --git a/Tests/UnitTestsParallelizable/Views/ShortcutTests.cs b/Tests/UnitTestsParallelizable/Views/ShortcutTests.cs index c2bc2486ca..456adc4bed 100644 --- a/Tests/UnitTestsParallelizable/Views/ShortcutTests.cs +++ b/Tests/UnitTestsParallelizable/Views/ShortcutTests.cs @@ -54,7 +54,7 @@ public void Constructor_Defaults () // CanFocus defaults Assert.True (shortcut.CanFocus); - Assert.False (shortcut.CommandView.CanFocus); + Assert.False (shortcut.CommandView?.CanFocus); // Dimension defaults Assert.IsType (shortcut.Width); @@ -77,7 +77,7 @@ public void Constructor_Defaults () // CommandView defaults Assert.NotNull (shortcut.CommandView); #if DEBUG - Assert.Equal ("CommandView", shortcut.CommandView.Id); + Assert.Equal ("CommandView", shortcut.CommandView?.Id); #endif // HelpView defaults @@ -122,7 +122,7 @@ public void Constructor_Defaults () Assert.False (shortcut1.BindKeyToApplication); Assert.Equal (Orientation.Horizontal, shortcut1.Orientation); Assert.Equal (AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast, shortcut1.AlignmentModes); - Assert.Equal ("_CommandText", shortcut1.CommandView.Text); // Title syncs to CommandView.Text + Assert.Equal ("_CommandText", shortcut1.CommandView?.Text); // Title syncs to CommandView?.Text Assert.Equal (MouseState.In, shortcut1.MouseHighlightStates); // SubViews - All three should be present with values set @@ -145,14 +145,14 @@ public void Size_Defaults () Assert.Equal (2, shortcut.Viewport.Width); Assert.Equal (1, shortcut.Viewport.Height); - Assert.Equal (0, shortcut.CommandView.Viewport.Width); - Assert.Equal (1, shortcut.CommandView.Viewport.Height); + Assert.Equal (0, shortcut.CommandView?.Viewport.Width); + Assert.Equal (1, shortcut.CommandView?.Viewport.Height); - Assert.Equal (0, shortcut.HelpView.Viewport.Width); - Assert.Equal (0, shortcut.HelpView.Viewport.Height); + Assert.Equal (2, shortcut.HelpView.Viewport.Width); + Assert.Equal (1, shortcut.HelpView.Viewport.Height); - Assert.Equal (0, shortcut.KeyView.Viewport.Width); - Assert.Equal (0, shortcut.KeyView.Viewport.Height); + Assert.Equal (2, shortcut.KeyView.Viewport.Width); + Assert.Equal (1, shortcut.KeyView.Viewport.Height); // 0123456789 // " 0 A " @@ -163,8 +163,8 @@ public void Size_Defaults () Assert.Equal (8, shortcut.Viewport.Width); Assert.Equal (1, shortcut.Viewport.Height); - Assert.Equal (0, shortcut.CommandView.Viewport.Width); - Assert.Equal (1, shortcut.CommandView.Viewport.Height); + Assert.Equal (0, shortcut.CommandView?.Viewport.Width); + Assert.Equal (1, shortcut.CommandView?.Viewport.Height); Assert.Equal (1, shortcut.HelpView.Viewport.Width); Assert.Equal (1, shortcut.HelpView.Viewport.Height); @@ -181,8 +181,8 @@ public void Size_Defaults () Assert.Equal (9, shortcut.Viewport.Width); Assert.Equal (1, shortcut.Viewport.Height); - Assert.Equal (1, shortcut.CommandView.Viewport.Width); - Assert.Equal (1, shortcut.CommandView.Viewport.Height); + Assert.Equal (1, shortcut.CommandView?.Viewport.Width); + Assert.Equal (1, shortcut.CommandView?.Viewport.Height); Assert.Equal (1, shortcut.HelpView.Viewport.Width); Assert.Equal (1, shortcut.HelpView.Viewport.Height); @@ -261,7 +261,7 @@ public void Set_Width_Layouts_Correctly (int width, int expectedCmdX, int expect // 0123456789 // -C--H--K- - Assert.Equal (expectedCmdX, shortcut.CommandView.Frame.X); + Assert.Equal (expectedCmdX, shortcut.CommandView?.Frame.X); Assert.Equal (expectedHelpX, shortcut.HelpView.Frame.X); Assert.Equal (expectedKeyX, shortcut.KeyView.Frame.X); } @@ -271,19 +271,19 @@ public void CommandView_Text_And_Title_Track () { var shortcut = new Shortcut { Title = "T" }; - Assert.Equal (shortcut.Title, shortcut.CommandView.Text); + Assert.Equal (shortcut.Title, shortcut.CommandView?.Text); shortcut = new Shortcut (); shortcut.CommandView = new View { Text = "T" }; - Assert.Equal (shortcut.Title, shortcut.CommandView.Text); + Assert.Equal (shortcut.Title, shortcut.CommandView?.Text); } [Fact] public void HotKey_Title_Initializer_SetsCorrectly () { Shortcut shortcut = new () { Title = "_C" }; - Assert.Equal (Key.Empty, shortcut.CommandView.HotKey); + Assert.Equal (Key.Empty, shortcut.CommandView?.HotKey); Assert.Equal (Key.C, shortcut.HotKey); } @@ -293,15 +293,15 @@ public void HotKey_CommandView_Set_Sets_Correctly () Shortcut shortcut = new () { Title = "_C" }; shortcut.CommandView = new View { HotKeySpecifier = (Rune)'_', Text = "_D" }; - Assert.Equal (Key.Empty, shortcut.CommandView.HotKey); + Assert.Equal (Key.Empty, shortcut.CommandView?.HotKey); Assert.Equal (Key.D, shortcut.HotKey); shortcut = new Shortcut { Title = "_C", CommandView = new View { HotKeySpecifier = (Rune)'_', Text = "_D" } }; - Assert.Equal (Key.Empty, shortcut.CommandView.HotKey); + Assert.Equal (Key.Empty, shortcut.CommandView?.HotKey); Assert.Equal (Key.D, shortcut.HotKey); shortcut = new Shortcut { CommandView = new View { HotKeySpecifier = (Rune)'_', Text = "_D" }, Title = "_C" }; - Assert.Equal (Key.Empty, shortcut.CommandView.HotKey); + Assert.Equal (Key.Empty, shortcut.CommandView?.HotKey); Assert.Equal (Key.C, shortcut.HotKey); } @@ -456,7 +456,7 @@ public void SubView_Visibility_Controlled_By_Removal () { Shortcut shortcut = new (); - Assert.True (shortcut.CommandView.Visible); + Assert.True (shortcut.CommandView?.Visible); Assert.Contains (shortcut.CommandView, shortcut.SubViews); Assert.True (shortcut.HelpView.Visible); Assert.DoesNotContain (shortcut.HelpView, shortcut.SubViews); @@ -498,7 +498,7 @@ public void Focus_CanFocus_Default_Is_True () shortcut.Text = "Help"; shortcut.Title = "Command"; Assert.True (shortcut.CanFocus); - Assert.False (shortcut.CommandView.CanFocus); + Assert.False (shortcut.CommandView?.CanFocus); } [Fact] @@ -506,25 +506,25 @@ public void Focus_CanFocus_CommandView_Add_Tracks () { Shortcut shortcut = new (); Assert.True (shortcut.CanFocus); - Assert.False (shortcut.CommandView.CanFocus); + Assert.False (shortcut.CommandView?.CanFocus); shortcut.CommandView = new View { CanFocus = true }; - Assert.True (shortcut.CommandView.CanFocus); + Assert.True (shortcut.CommandView?.CanFocus); - shortcut.CommandView.CanFocus = true; - Assert.True (shortcut.CommandView.CanFocus); + shortcut.CommandView?.CanFocus = true; + Assert.True (shortcut.CommandView?.CanFocus); shortcut.CanFocus = false; Assert.False (shortcut.CanFocus); - Assert.True (shortcut.CommandView.CanFocus); + Assert.True (shortcut.CommandView?.CanFocus); - shortcut.CommandView.CanFocus = false; + shortcut.CommandView?.CanFocus = false; Assert.False (shortcut.CanFocus); - Assert.False (shortcut.CommandView.CanFocus); + Assert.False (shortcut.CommandView?.CanFocus); - shortcut.CommandView.CanFocus = true; + shortcut.CommandView?.CanFocus = true; Assert.False (shortcut.CanFocus); - Assert.True (shortcut.CommandView.CanFocus); + Assert.True (shortcut.CommandView?.CanFocus); } // Claude - Opus 4.5