From 0a6a41d570a01232cb6f38f1d970573c4284b2ed Mon Sep 17 00:00:00 2001 From: Tig Date: Mon, 16 Sep 2024 09:18:20 -0600 Subject: [PATCH] WIP: Working through adornment focus stuff. --- .../Application/ApplicationNavigation.cs | 5 + Terminal.Gui/View/Adornment/Border.cs | 435 ++++++++++++------ Terminal.Gui/View/View.Drawing.cs | 9 +- Terminal.Gui/View/View.Navigation.cs | 115 +++-- UnitTests/View/Navigation/SetFocusTests.cs | 109 +++++ 5 files changed, 479 insertions(+), 194 deletions(-) diff --git a/Terminal.Gui/Application/ApplicationNavigation.cs b/Terminal.Gui/Application/ApplicationNavigation.cs index d472eee13..20965adbf 100644 --- a/Terminal.Gui/Application/ApplicationNavigation.cs +++ b/Terminal.Gui/Application/ApplicationNavigation.cs @@ -62,6 +62,11 @@ public class ApplicationNavigation } } + //if (start.Border is { }) + //{ + // return IsInHierarchy (start.Border, view); + //} + return false; } diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 4fc92daed..de7390c9f 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -59,6 +59,7 @@ public class Border : Adornment { Parent = parent; CanFocus = false; + TabStop = TabBehavior.TabGroup; Application.GrabbingMouse += Application_GrabbingMouse; Application.UnGrabbingMouse += Application_UnGrabbingMouse; @@ -291,22 +292,22 @@ public class Border : Adornment return true; } - if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable) - && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) - && !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) - && !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable) - && !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable) - ) - { - return false; - } - // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/3312 if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) { Parent.SetFocus (); ApplicationOverlapped.BringOverlappedTopToFront (); + if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable) + && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) + && !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) + && !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable) + && !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable) + ) + { + return false; + } + // Only start grabbing if the user clicks in the Thickness area // Adornment.Contains takes Parent SuperView=relative coords. if (Contains (new (mouseEvent.Position.X + Parent.Frame.X + Frame.X, mouseEvent.Position.Y + Parent.Frame.Y + Frame.Y))) @@ -930,11 +931,16 @@ public class Border : Adornment private ViewArrangement _arranging; - private Button? _arrangeButton; + private Button? _moveButton; // always top-left + private Button? _allSizeButton; + private Button? _leftSizeButton; + private Button? _rightSizeButton; + private Button? _topSizeButton; + private Button? _bottomSizeButton; /// /// Starts "Arrange Mode" where can be moved and/or resized using the mouse - /// or keyboard. + /// or keyboard. If is keyboard mode is enabled. /// /// /// Arrange Mode is exited by the user pressing , , or by clicking @@ -955,21 +961,231 @@ public class Border : Adornment return false; } - Debug.Assert (_arrangeButton is null); - _arrangeButton = new Button + // Add Commands and Keybindigs - Note it's ok these get added each time. KeyBindings are cleared in EndArrange() + AddArrangeModeKeyBindings (); + + Application.MouseEvent += ApplicationOnMouseEvent; + + // Create buttons for resizing and moving + if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable)) { - CanFocus = true, - Width = 1, - Height = 1, - NoDecorations = true, - NoPadding = true, - ShadowStyle = ShadowStyle.None, - Text = $"{Glyphs.Diamond}", - }; - Add (_arrangeButton); + Debug.Assert (_moveButton is null); - CanFocus = true; + _moveButton = new Button + { + Id = "moveButton", + CanFocus = true, + Width = 1, + Height = 1, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None, + Text = $"{Glyphs.Diamond}", + Visible = false, + }; + Add (_moveButton); + } + if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable)) + { + Debug.Assert (_allSizeButton is null); + + _allSizeButton = new Button + { + Id = "allSizeButton", + CanFocus = true, + Width = 1, + Height = 1, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None, + Text = $"{Glyphs.Diamond}", + X = Pos.AnchorEnd (), + Y = Pos.AnchorEnd (), + Visible = false, + }; + Add (_allSizeButton); + } + + if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)) + { + Debug.Assert (_topSizeButton is null); + + _topSizeButton = new Button + { + Id = "topSizeButton", + CanFocus = true, + Width = 1, + Height = 1, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None, + Text = $"{Glyphs.Diamond}", + X = Pos.Center () + Parent!.Margin.Thickness.Horizontal, + Y = 0, + Visible = false, + }; + Add (_topSizeButton); + } + + if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable)) + { + Debug.Assert (_rightSizeButton is null); + + _rightSizeButton = new Button + { + Id = "rightSizeButton", + CanFocus = true, + Width = 1, + Height = 1, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None, + Text = $"{Glyphs.Diamond}", + X = Pos.AnchorEnd (), + Y = Pos.Center () + Parent!.Margin.Thickness.Vertical, + Visible = false, + }; + Add (_rightSizeButton); + } + + if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)) + { + Debug.Assert (_leftSizeButton is null); + + _leftSizeButton = new Button + { + Id = "leftSizeButton", + CanFocus = true, + Width = 1, + Height = 1, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None, + Text = $"{Glyphs.Diamond}", + X = 0, + Y = Pos.Center () + Parent!.Margin.Thickness.Vertical, + Visible = false, + }; + Add (_leftSizeButton); + } + + if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)) + { + Debug.Assert (_bottomSizeButton is null); + + _bottomSizeButton = new Button + { + Id = "bottomSizeButton", + CanFocus = true, + Width = 1, + Height = 1, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None, + Text = $"{Glyphs.Diamond}", + X = Pos.Center () + Parent!.Margin.Thickness.Horizontal, + Y = Pos.AnchorEnd (), + Visible = false, + }; + Add (_bottomSizeButton); + } + + + if (arrangement == ViewArrangement.Fixed) + { + // Keyboard mode + if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable)) + { + _arranging = ViewArrangement.Movable; + _moveButton!.Visible = true; + } + + if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable)) + { + _arranging = ViewArrangement.Resizable; + _allSizeButton!.Visible = true; + } + } + else + { + // Mouse mode + _arranging = arrangement; + + switch (_arranging) + { + case ViewArrangement.Movable: + _moveButton!.Visible = true; + break; + + case ViewArrangement.RightResizable | ViewArrangement.BottomResizable: + case ViewArrangement.Resizable: + _rightSizeButton!.Visible = true; + _bottomSizeButton!.Visible = true; + _allSizeButton!.X = Pos.AnchorEnd (); + _allSizeButton!.Y = Pos.AnchorEnd (); + _allSizeButton!.Visible = true; + break; + + case ViewArrangement.LeftResizable: + _leftSizeButton!.Visible = true; + break; + + case ViewArrangement.RightResizable: + _rightSizeButton!.Visible = true; + break; + + case ViewArrangement.TopResizable: + _topSizeButton!.Visible = true; + break; + + case ViewArrangement.BottomResizable: + _bottomSizeButton!.Visible = true; + break; + + case ViewArrangement.LeftResizable | ViewArrangement.BottomResizable: + _rightSizeButton!.Visible = true; + _bottomSizeButton!.Visible = true; + _allSizeButton!.X = 0; + _allSizeButton!.Y = Pos.AnchorEnd (); + _allSizeButton!.Visible = true; + break; + + case ViewArrangement.LeftResizable | ViewArrangement.TopResizable: + _leftSizeButton!.Visible = true; + _topSizeButton!.Visible = true; + break; + + case ViewArrangement.RightResizable | ViewArrangement.TopResizable: + _rightSizeButton!.Visible = true; + _topSizeButton!.Visible = true; + _allSizeButton!.X = Pos.AnchorEnd (); + _allSizeButton!.Y = 0; + _allSizeButton!.Visible = true; + + break; + + } + } + + if (_arranging != ViewArrangement.Fixed) + { + if (arrangement == ViewArrangement.Fixed) + { + // Keyboard mode - enable nav + CanFocus = true; + SetFocus (); + } + return true; + } + + // Hack for now + EndArrange (); + return false; + } + + private void AddArrangeModeKeyBindings () + { AddCommand (Command.Quit, EndArrange); AddCommand (Command.Up, @@ -1060,8 +1276,18 @@ public class Border : Adornment return true; }); - AddCommand (Command.Tab, Navigate); - AddCommand (Command.BackTab, Navigate); + AddCommand (Command.Tab, () => + { + AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop); + + return true; // Always eat + }); + AddCommand (Command.BackTab, () => + { + AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop); + + return true; // Always eat + }); KeyBindings.Add (Key.Esc, KeyBindingScope.HotKey, Command.Quit); KeyBindings.Add (Application.ArrangeKey, KeyBindingScope.HotKey, Command.Quit); @@ -1072,113 +1298,6 @@ public class Border : Adornment KeyBindings.Add (Key.Tab, KeyBindingScope.HotKey, Command.Tab); KeyBindings.Add (Key.Tab.WithShift, KeyBindingScope.HotKey, Command.BackTab); - - Application.MouseEvent += ApplicationOnMouseEvent; - - if (arrangement == ViewArrangement.Fixed) - { - if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable)) - { - _arranging = ViewArrangement.Movable; - _arrangeButton.X = 0; - _arrangeButton.Y = 0; - return true; - } - - if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable)) - { - _arranging = ViewArrangement.Resizable; - _arrangeButton.X = Pos.AnchorEnd (); - _arrangeButton.Y = Pos.AnchorEnd (); - - return true; - } - } - else - { - _arranging = arrangement; - - switch (_arranging) - { - case ViewArrangement.Movable: - _arrangeButton.X = 0; - _arrangeButton.Y = 0; - return true; - - case ViewArrangement.RightResizable | ViewArrangement.BottomResizable: - case ViewArrangement.Resizable: - _arrangeButton.X = Pos.AnchorEnd (); - _arrangeButton.Y = Pos.AnchorEnd (); - return true; - - case ViewArrangement.LeftResizable: - _arrangeButton.X = 0; - _arrangeButton.Y = Pos.Center () + Parent!.Margin.Thickness.Vertical; - return true; - - case ViewArrangement.RightResizable: - _arrangeButton.X = Pos.AnchorEnd (); - _arrangeButton.Y = Pos.Center (); - return true; - - case ViewArrangement.TopResizable: - _arrangeButton.X = Pos.Center (); - _arrangeButton.Y = 0; - return true; - - case ViewArrangement.BottomResizable: - _arrangeButton.X = Pos.Center (); - _arrangeButton.Y = Pos.AnchorEnd (); - return true; - - case ViewArrangement.LeftResizable | ViewArrangement.BottomResizable: - _arrangeButton.X = 0; - _arrangeButton.Y = Pos.AnchorEnd (); - - return true; - - case ViewArrangement.LeftResizable | ViewArrangement.TopResizable: - _arrangeButton.X = 0; - _arrangeButton.Y = 0; - - return true; - - case ViewArrangement.RightResizable | ViewArrangement.TopResizable: - _arrangeButton.X = Pos.AnchorEnd (); ; - _arrangeButton.Y = 0; - - return true; - - } - } - - // Hack for now - EndArrange (); - return false; - - bool? Navigate () - { - if (_arranging == ViewArrangement.Movable) - { - if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable)) - { - _arranging = ViewArrangement.Resizable; - _arrangeButton.X = Pos.AnchorEnd (); - _arrangeButton.Y = Pos.AnchorEnd (); - } - } - else if (_arranging == ViewArrangement.Resizable) - { - if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable)) - { - _arranging = ViewArrangement.Movable; - _arrangeButton.X = 0; - _arrangeButton.Y = 0; - } - } - - return true; - } } private void ApplicationOnMouseEvent (object? sender, MouseEvent e) @@ -1210,17 +1329,55 @@ public class Border : Adornment Application.UngrabMouse (); } - CanFocus = false; - - if (_arrangeButton is { }) + if (_moveButton is { }) { - Remove (_arrangeButton); - _arrangeButton.Dispose (); - _arrangeButton = null; + Remove (_moveButton); + _moveButton.Dispose (); + _moveButton = null; + } + + if (_allSizeButton is { }) + { + Remove (_allSizeButton); + _allSizeButton.Dispose (); + _allSizeButton = null; + } + + if (_leftSizeButton is { }) + { + Remove (_leftSizeButton); + _leftSizeButton.Dispose (); + _leftSizeButton = null; + } + + if (_rightSizeButton is { }) + { + Remove (_rightSizeButton); + _rightSizeButton.Dispose (); + _rightSizeButton = null; + } + + if (_topSizeButton is { }) + { + Remove (_topSizeButton); + _topSizeButton.Dispose (); + _topSizeButton = null; + } + + if (_bottomSizeButton is { }) + { + Remove (_bottomSizeButton); + _bottomSizeButton.Dispose (); + _bottomSizeButton = null; } KeyBindings.Clear (); + if (CanFocus) + { + CanFocus = false; + } + return true; } diff --git a/Terminal.Gui/View/View.Drawing.cs b/Terminal.Gui/View/View.Drawing.cs index 9827071f8..0a6e76e41 100644 --- a/Terminal.Gui/View/View.Drawing.cs +++ b/Terminal.Gui/View/View.Drawing.cs @@ -473,15 +473,16 @@ public partial class View // Drawing APIs { if (NeedsDisplay) { + if (!CanBeVisible (this)) + { + return; + } + if (SuperView is { }) { Clear (); } - if (!CanBeVisible (this)) - { - return; - } if (!string.IsNullOrEmpty (TextFormatter.Text)) { diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs index 7aa658a16..d38d5c4f3 100644 --- a/Terminal.Gui/View/View.Navigation.cs +++ b/Terminal.Gui/View/View.Navigation.cs @@ -214,7 +214,33 @@ public partial class View // Focus and cross-view navigation management (TabStop /// Gets the currently focused Subview of this view, or if nothing is focused. public View? Focused { - get { return Subviews.FirstOrDefault (v => v.HasFocus); } + get + { + View? focused = Subviews.FirstOrDefault (v => v.HasFocus); + + if (focused is { }) + { + return focused; + } + + // How about in Adornments? + if (Margin is { HasFocus:true }) + { + return Margin; + } + + if (Border is { HasFocus: true }) + { + return Border; + } + + if (Padding is { HasFocus: true }) + { + return Padding; + } + + return null; + } } /// Returns a value indicating if this View is currently on Top (Active) @@ -384,7 +410,10 @@ public partial class View // Focus and cross-view navigation management (TabStop return (false, false); } - if (CanFocus && SuperView is { CanFocus: false }) + var thisAsAdornment = this as Adornment; + View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView; + + if (CanFocus && superViewOrParent is { CanFocus: false }) { Debug.WriteLine ($@"WARNING: Attempt to FocusChanging where SuperView.CanFocus == false. {this}"); @@ -412,7 +441,7 @@ public partial class View // Focus and cross-view navigation management (TabStop // Make sure superviews up the superview hierarchy have focus. // Any of them may cancel gaining focus. In which case we need to back out. - if (SuperView is { HasFocus: false } sv) + if (superViewOrParent is { HasFocus: false } sv) { (bool focusSet, bool svCancelled) = sv.SetHasFocusTrue (previousFocusedView, true); @@ -422,20 +451,6 @@ public partial class View // Focus and cross-view navigation management (TabStop } } - // Are we an Adornment? - if (this is Adornment adornment) - { - if (adornment.Parent is { HasFocus: false } parent) - { - (bool focusSet, bool parentCancelled) = parent.SetHasFocusTrue (previousFocusedView, true); - - if (!focusSet) - { - return (false, parentCancelled); - } - } - } - if (_hasFocus) { // Something else beat us to the change (likely a FocusChanged handler). @@ -445,16 +460,7 @@ public partial class View // Focus and cross-view navigation management (TabStop // By setting _hasFocus to true we definitively change HasFocus for this view. // Get whatever peer has focus, if any - View? focusedPeer = SuperView?.Focused; - - if (focusedPeer is null) - { - // Are we an Adornment? - if (this is Adornment ad) - { - focusedPeer = ad.Parent?.Focused; - } - } + View? focusedPeer = superViewOrParent?.Focused; _hasFocus = true; @@ -481,6 +487,14 @@ public partial class View // Focus and cross-view navigation management (TabStop previousFocusedView.SetHasFocusFalse (this); } + if (previousFocusedView is { HasFocus: true }) + { + if (previousFocusedView.SuperView is Adornment a) + { + previousFocusedView.SetHasFocusFalse (this); + } + } + if (Arrangement.HasFlag (ViewArrangement.Overlapped)) { SuperView?.MoveSubviewToEnd (this); @@ -570,43 +584,34 @@ public partial class View // Focus and cross-view navigation management (TabStop throw new InvalidOperationException ("SetHasFocusFalse should not be called if the view does not have focus."); } + var thisAsAdornment = this as Adornment; + View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView; + // If newFocusedVew is null, we need to find the view that should get focus, and SetFocus on it. if (!traversingDown && newFocusedView is null) { - if (SuperView?._previouslyMostFocused is { }) + if (superViewOrParent?._previouslyMostFocused is { }) { - if (SuperView?._previouslyMostFocused != this) + if (superViewOrParent?._previouslyMostFocused != this) { - SuperView?._previouslyMostFocused?.SetFocus (); + superViewOrParent?._previouslyMostFocused?.SetFocus (); // The above will cause SetHasFocusFalse, so we can return return; } - newFocusedView = SuperView?._previouslyMostFocused; + + newFocusedView = superViewOrParent?._previouslyMostFocused; } - if (SuperView is { }) + if (superViewOrParent is { }) { - if (SuperView.AdvanceFocus (NavigationDirection.Forward, TabStop)) + if (superViewOrParent.AdvanceFocus (NavigationDirection.Forward, TabStop)) { // The above will cause SetHasFocusFalse, so we can return return; } - newFocusedView = SuperView; - } - // Are we an Adornment? - if (this is Adornment ad) - { - if (ad.Parent is {}) - { - if (ad.Parent.RestoreFocus ()) - { - // The above will cause SetHasFocusFalse, so we can return - return; - } - newFocusedView = ad.Parent; - } + newFocusedView = superViewOrParent; } if (Application.Navigation is { } && Application.Current is { }) @@ -645,6 +650,13 @@ public partial class View // Focus and cross-view navigation management (TabStop bottom = bottom.SuperView; } + if (bottom == this && bottom.SuperView is Adornment a) + { + a.SetHasFocusFalse (newFocusedView, true); + } + + Debug.Assert (!mostFocused.WasDisposed); + _previouslyMostFocused = mostFocused; } @@ -654,7 +666,7 @@ public partial class View // Focus and cross-view navigation management (TabStop NotifyFocusChanging (HasFocus, !HasFocus, newFocusedView, this); // Get whatever peer has focus, if any - View? focusedPeer = SuperView?.Focused; + View? focusedPeer = superViewOrParent?.Focused; _hasFocus = false; if (Application.Navigation is { }) @@ -663,7 +675,7 @@ public partial class View // Focus and cross-view navigation management (TabStop if (appFocused is { } || appFocused == this) { - Application.Navigation.SetFocused (newFocusedView ?? SuperView); + Application.Navigation.SetFocused (newFocusedView ?? superViewOrParent); } } @@ -675,9 +687,10 @@ public partial class View // Focus and cross-view navigation management (TabStop return; } - if (SuperView is { }) + if (superViewOrParent is { }) { - SuperView._previouslyMostFocused = focusedPeer; + Debug.Assert(!focusedPeer.WasDisposed); + superViewOrParent._previouslyMostFocused = focusedPeer; } // Post-conditions - prove correctness diff --git a/UnitTests/View/Navigation/SetFocusTests.cs b/UnitTests/View/Navigation/SetFocusTests.cs index 3896e51cb..8dfd6375a 100644 --- a/UnitTests/View/Navigation/SetFocusTests.cs +++ b/UnitTests/View/Navigation/SetFocusTests.cs @@ -190,6 +190,115 @@ public class SetFocusTests () : TestsAllViews Assert.False (subViewSubView3.HasFocus); } + + [Fact] + public void SetFocus_AdornmentSubView_SetFocus_Sets () + { + var view = new View + { + Id = "view", + CanFocus = true + }; + + var subView = new View + { + Id = "subView", + CanFocus = true + }; + + view.Add (subView); + + var borderSubView = new View + { + Id = "borderSubView", + CanFocus = true + }; + + + var subViewSubView1 = new View + { + Id = "subViewSubView1", + CanFocus = true + }; + + var subViewSubView2 = new View + { + Id = "subViewSubView2", + CanFocus = true + }; + + var subViewSubView3 = new View + { + Id = "subViewSubView3", + CanFocus = true + }; + borderSubView.Add (subViewSubView1, subViewSubView2, subViewSubView3); + + view.Border.Add (borderSubView); + + view.SetFocus (); + Assert.True (view.HasFocus); + Assert.True (subView.HasFocus); + Assert.False (borderSubView.HasFocus); + + view.Border.CanFocus = true; + subViewSubView1.SetFocus (); + Assert.True (view.HasFocus); + Assert.False (subView.HasFocus); + Assert.True (borderSubView.HasFocus); + Assert.True (subViewSubView1.HasFocus); + Assert.False (subViewSubView2.HasFocus); + Assert.False (subViewSubView3.HasFocus); + + view.Border.CanFocus = false; + Assert.True (view.HasFocus); + Assert.True (subView.HasFocus); + Assert.False (view.Border.HasFocus); + Assert.False (borderSubView.HasFocus); + Assert.False (subViewSubView1.HasFocus); + Assert.False (subViewSubView2.HasFocus); + Assert.False (subViewSubView3.HasFocus); + + view.Border.CanFocus = true; + view.Border.SetFocus (); + Assert.True (view.HasFocus); + Assert.True (view.Border.HasFocus); + Assert.False (subView.HasFocus); + Assert.True (borderSubView.HasFocus); + Assert.True (subViewSubView1.HasFocus); + Assert.False (subViewSubView2.HasFocus); + Assert.False (subViewSubView3.HasFocus); + + view.Border.CanFocus = false; + Assert.True (view.HasFocus); + Assert.True (subView.HasFocus); + Assert.False (view.Border.HasFocus); + Assert.False (borderSubView.HasFocus); + Assert.False (subViewSubView1.HasFocus); + Assert.False (subViewSubView2.HasFocus); + Assert.False (subViewSubView3.HasFocus); + + view.Border.CanFocus = true; + subViewSubView1.SetFocus (); + Assert.True (view.HasFocus); + Assert.False (subView.HasFocus); + Assert.True (view.Border.HasFocus); + Assert.True (borderSubView.HasFocus); + Assert.True (subViewSubView1.HasFocus); + Assert.False (subViewSubView2.HasFocus); + Assert.False (subViewSubView3.HasFocus); + + subView.SetFocus (); + Assert.True (view.HasFocus); + Assert.True (subView.HasFocus); + Assert.False (view.Border.HasFocus); + Assert.False (borderSubView.HasFocus); + Assert.False (subViewSubView1.HasFocus); + Assert.False (subViewSubView2.HasFocus); + Assert.False (subViewSubView3.HasFocus); + } + + [Fact] public void SetFocus_Peer_LeavesOther () {