diff --git a/Terminal.Gui/Drawing/Glyphs.cs b/Terminal.Gui/Drawing/Glyphs.cs
index f64285f0f..fdf024307 100644
--- a/Terminal.Gui/Drawing/Glyphs.cs
+++ b/Terminal.Gui/Drawing/Glyphs.cs
@@ -106,6 +106,27 @@ public class GlyphDefinitions
/// Identical To (U+226)
public Rune IdenticalTo { get; set; } = (Rune)'≡';
+ /// Move indicator. Default is Lozenge (U+25CA) - ◊.
+ public Rune Move { get; set; } = (Rune)'◊';
+
+ /// Size Horizontally indicator. Default is ┥Left Right Arrow - ↔ U+02194
+ public Rune SizeHorizontal { get; set; } = (Rune)'↔';
+
+ /// Size Vertical indicator. Default Up Down Arrow - ↕ U+02195
+ public Rune SizeVertical { get; set; } = (Rune)'↕';
+
+ /// Size Top Left indicator. North West Arrow - ↖ U+02196
+ public Rune SizeTopLeft { get; set; } = (Rune)'↖';
+
+ /// Size Top Right indicator. North East Arrow - ↗ U+02197
+ public Rune SizeTopRight { get; set; } = (Rune)'↗';
+
+ /// Size Bottom Right indicator. South East Arrow - ↘ U+02198
+ public Rune SizeBottomRight { get; set; } = (Rune)'↘';
+
+ /// Size Bottom Left indicator. South West Arrow - ↙ U+02199
+ public Rune SizeBottomLLeft { get; set; } = (Rune)'↙';
+
/// Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610.
public Rune Apple { get; set; } = "🍎".ToRunes () [0]; // nonBMP
diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs
index 6c575ad99..791d2e77f 100644
--- a/Terminal.Gui/View/Adornment/Adornment.cs
+++ b/Terminal.Gui/View/Adornment/Adornment.cs
@@ -26,7 +26,8 @@ public class Adornment : View
///
public Adornment (View parent)
{
- CanFocus = true;
+ // By default Adornments can't get focus; has to be enabled specifically.
+ CanFocus = false;
Parent = parent;
}
diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs
index de7390c9f..838760417 100644
--- a/Terminal.Gui/View/Adornment/Border.cs
+++ b/Terminal.Gui/View/Adornment/Border.cs
@@ -1,7 +1,5 @@
#nullable enable
using System.Diagnostics;
-using Microsoft.CodeAnalysis;
-using static Terminal.Gui.SpinnerStyle;
namespace Terminal.Gui;
@@ -68,7 +66,6 @@ public class Border : Adornment
Highlight += Border_Highlight;
}
-
#if SUBVIEW_BASED_BORDER
private Line _left;
@@ -78,7 +75,6 @@ public class Border : Adornment
public Button CloseButton { get; internal set; }
#endif
-
///
public override void BeginInit ()
{
@@ -117,11 +113,8 @@ public class Border : Adornment
LayoutStarted += OnLayoutStarted;
}
#endif
-
}
-
-
#if SUBVIEW_BASED_BORDER
private void OnLayoutStarted (object sender, LayoutEventArgs e)
{
@@ -164,6 +157,7 @@ public class Border : Adornment
internal Rectangle GetBorderRectangle ()
{
Rectangle screenRect = ViewportToScreen (Viewport);
+
return new (
screenRect.X + Math.Max (0, Thickness.Left - 1),
screenRect.Y + Math.Max (0, Thickness.Top - 1),
@@ -312,6 +306,11 @@ public class Border : Adornment
// 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)))
{
+ if (_arranging != ViewArrangement.Fixed)
+ {
+ EndArrangeMode ();
+ }
+
// Set the start grab point to the Frame coords
_startGrabPoint = new (mouseEvent.Position.X + Frame.X, mouseEvent.Position.Y + Frame.Y);
_dragPosition = mouseEvent.Position;
@@ -319,23 +318,19 @@ public class Border : Adornment
SetHighlight (HighlightStyle);
+ // If not resizable, but movable: Drag anywhere is move
// If resizable and movable: Drag on top is move, other 3 sides are size
// If not movable, but resizable: Drag on any side sizes.
// Get rectangle representing Thickness.Top
// If mouse is in that rectangle, set _arranging to ViewArrangement.Movable
- Rectangle sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
- if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable) && sideRect.Contains (_startGrabPoint))
- {
- EnterArrangeMode (ViewArrangement.Movable);
-
- return true;
- }
+ Rectangle sideRect;
// If mouse is in any other rectangle, set _arranging to ViewArrangement.
if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
{
sideRect = new (Frame.X, Frame.Y + Thickness.Top, Thickness.Left, Frame.Height - Thickness.Top - Thickness.Bottom);
+
if (sideRect.Contains (_startGrabPoint))
{
EnterArrangeMode (ViewArrangement.LeftResizable);
@@ -346,7 +341,12 @@ public class Border : Adornment
if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
{
- sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Y + Thickness.Top, Thickness.Right, Frame.Height - Thickness.Top - Thickness.Bottom);
+ sideRect = new (
+ Frame.X + Frame.Width - Thickness.Right,
+ Frame.Y + Thickness.Top,
+ Thickness.Right,
+ Frame.Height - Thickness.Top - Thickness.Bottom);
+
if (sideRect.Contains (_startGrabPoint))
{
EnterArrangeMode (ViewArrangement.RightResizable);
@@ -355,9 +355,10 @@ public class Border : Adornment
}
}
- if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable))
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && !Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
{
sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
+
if (sideRect.Contains (_startGrabPoint))
{
EnterArrangeMode (ViewArrangement.TopResizable);
@@ -368,7 +369,12 @@ public class Border : Adornment
if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable))
{
- sideRect = new (Frame.X + Thickness.Left, Frame.Y + Frame.Height - Thickness.Bottom, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Bottom);
+ sideRect = new (
+ Frame.X + Thickness.Left,
+ Frame.Y + Frame.Height - Thickness.Bottom,
+ Frame.Width - Thickness.Left - Thickness.Right,
+ Thickness.Bottom);
+
if (sideRect.Contains (_startGrabPoint))
{
EnterArrangeMode (ViewArrangement.BottomResizable);
@@ -377,10 +383,10 @@ public class Border : Adornment
}
}
-
if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
{
sideRect = new (Frame.X, Frame.Height - Thickness.Top, Thickness.Left, Thickness.Bottom);
+
if (sideRect.Contains (_startGrabPoint))
{
EnterArrangeMode (ViewArrangement.BottomResizable | ViewArrangement.LeftResizable);
@@ -392,6 +398,7 @@ public class Border : Adornment
if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
{
sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Height - Thickness.Top, Thickness.Right, Thickness.Bottom);
+
if (sideRect.Contains (_startGrabPoint))
{
EnterArrangeMode (ViewArrangement.BottomResizable | ViewArrangement.RightResizable);
@@ -403,6 +410,7 @@ public class Border : Adornment
if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
{
sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Y, Thickness.Right, Thickness.Top);
+
if (sideRect.Contains (_startGrabPoint))
{
EnterArrangeMode (ViewArrangement.TopResizable | ViewArrangement.RightResizable);
@@ -414,6 +422,7 @@ public class Border : Adornment
if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
{
sideRect = new (Frame.X, Frame.Y, Thickness.Left, Thickness.Top);
+
if (sideRect.Contains (_startGrabPoint))
{
EnterArrangeMode (ViewArrangement.TopResizable | ViewArrangement.LeftResizable);
@@ -422,7 +431,17 @@ public class Border : Adornment
}
}
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+ {
+ //sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
+ //if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.Movable);
+
+ return true;
+ }
+ }
}
return true;
@@ -449,11 +468,11 @@ public class Border : Adornment
int minHeight = Thickness.Vertical + Parent!.Margin.Thickness.Bottom;
int minWidth = Thickness.Horizontal + Parent!.Margin.Thickness.Right;
+
switch (_arranging)
{
case ViewArrangement.Movable:
-
GetLocationEnsuringFullVisibility (
Parent,
parentLoc.X - _startGrabPoint.X,
@@ -484,6 +503,7 @@ public class Border : Adornment
case ViewArrangement.BottomResizable:
Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
+
break;
case ViewArrangement.LeftResizable:
@@ -497,6 +517,7 @@ public class Border : Adornment
Parent.Width = newWidth;
Parent.X = parentLoc.X - _startGrabPoint.X;
}
+
break;
case ViewArrangement.RightResizable:
@@ -521,6 +542,7 @@ public class Border : Adornment
}
Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
+
break;
case ViewArrangement.TopResizable | ViewArrangement.RightResizable:
@@ -534,6 +556,7 @@ public class Border : Adornment
}
Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
+
break;
case ViewArrangement.TopResizable | ViewArrangement.LeftResizable:
@@ -554,11 +577,10 @@ public class Border : Adornment
Parent.Width = newW2;
Parent.X = parentLoc.X - _startGrabPoint.X;
}
+
break;
-
}
-
return true;
}
}
@@ -569,7 +591,7 @@ public class Border : Adornment
Application.UngrabMouse ();
SetHighlight (HighlightStyle.None);
- EndArrange ();
+ EndArrangeMode ();
return true;
}
@@ -713,12 +735,12 @@ public class Border : Adornment
{
// ╔╡╞╗ should be ╔══╗
lc?.AddLine (
- new (borderBounds.Location.X, titleY),
- borderBounds.Width,
- Orientation.Horizontal,
- lineStyle,
- Driver.GetAttribute ()
- );
+ new (borderBounds.Location.X, titleY),
+ borderBounds.Width,
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
else
{
@@ -728,12 +750,12 @@ public class Border : Adornment
if (Thickness.Top == 2)
{
lc?.AddLine (
- new (borderBounds.X + 1, topTitleLineY),
- Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
- Orientation.Horizontal,
- lineStyle,
- Driver.GetAttribute ()
- );
+ new (borderBounds.X + 1, topTitleLineY),
+ Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
// ┌────┐
@@ -742,70 +764,70 @@ public class Border : Adornment
if (borderBounds.Width >= 4 && Thickness.Top > 2)
{
lc?.AddLine (
- new (borderBounds.X + 1, topTitleLineY),
- Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
- Orientation.Horizontal,
- lineStyle,
- Driver.GetAttribute ()
- );
+ new (borderBounds.X + 1, topTitleLineY),
+ Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
lc?.AddLine (
- new (borderBounds.X + 1, topTitleLineY + 2),
- Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
- Orientation.Horizontal,
- lineStyle,
- Driver.GetAttribute ()
- );
+ new (borderBounds.X + 1, topTitleLineY + 2),
+ Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
// ╔╡Title╞═════╗
// Add a short horiz line for ╔╡
lc?.AddLine (
- new (borderBounds.Location.X, titleY),
- 2,
- Orientation.Horizontal,
- lineStyle,
- Driver.GetAttribute ()
- );
+ new (borderBounds.Location.X, titleY),
+ 2,
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
// Add a vert line for ╔╡
lc?.AddLine (
- new (borderBounds.X + 1, topTitleLineY),
- titleBarsLength,
- Orientation.Vertical,
- LineStyle.Single,
- Driver.GetAttribute ()
- );
+ new (borderBounds.X + 1, topTitleLineY),
+ titleBarsLength,
+ Orientation.Vertical,
+ LineStyle.Single,
+ Driver.GetAttribute ()
+ );
// Add a vert line for ╞
lc?.AddLine (
- new (
- borderBounds.X
- + 1
- + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
- - 1,
- topTitleLineY
- ),
- titleBarsLength,
- Orientation.Vertical,
- LineStyle.Single,
- Driver.GetAttribute ()
- );
+ new (
+ borderBounds.X
+ + 1
+ + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
+ - 1,
+ topTitleLineY
+ ),
+ titleBarsLength,
+ Orientation.Vertical,
+ LineStyle.Single,
+ Driver.GetAttribute ()
+ );
// Add the right hand line for ╞═════╗
lc?.AddLine (
- new (
- borderBounds.X
- + 1
- + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
- - 1,
- titleY
- ),
- borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
- Orientation.Horizontal,
- lineStyle,
- Driver.GetAttribute ()
- );
+ new (
+ borderBounds.X
+ + 1
+ + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
+ - 1,
+ titleY
+ ),
+ borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
}
@@ -814,35 +836,35 @@ public class Border : Adornment
if (drawLeft)
{
lc?.AddLine (
- new (borderBounds.Location.X, titleY),
- sideLineLength,
- Orientation.Vertical,
- lineStyle,
- Driver.GetAttribute ()
- );
+ new (borderBounds.Location.X, titleY),
+ sideLineLength,
+ Orientation.Vertical,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
#endif
if (drawBottom)
{
lc?.AddLine (
- new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
- borderBounds.Width,
- Orientation.Horizontal,
- lineStyle,
- Driver.GetAttribute ()
- );
+ new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
+ borderBounds.Width,
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
if (drawRight)
{
lc?.AddLine (
- new (borderBounds.X + borderBounds.Width - 1, titleY),
- sideLineLength,
- Orientation.Vertical,
- lineStyle,
- Driver.GetAttribute ()
- );
+ new (borderBounds.X + borderBounds.Width - 1, titleY),
+ sideLineLength,
+ Orientation.Vertical,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
Driver.SetAttribute (prevAttr);
@@ -862,9 +884,9 @@ public class Border : Adornment
if (drawTop && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title))
{
Parent!.TitleTextFormatter.Draw (
- new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
- Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
- Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
+ new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
+ Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
+ Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
}
//Left
@@ -943,7 +965,8 @@ public class Border : Adornment
/// or keyboard. If is keyboard mode is enabled.
///
///
- /// Arrange Mode is exited by the user pressing , , or by clicking
+ /// Arrange Mode is exited by the user pressing , , or by
+ /// clicking
/// the mouse out of the 's Frame.
///
///
@@ -956,7 +979,7 @@ public class Border : Adornment
&& !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)
&& !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)
&& !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable)
- )
+ )
{
return false;
}
@@ -971,7 +994,7 @@ public class Border : Adornment
{
Debug.Assert (_moveButton is null);
- _moveButton = new Button
+ _moveButton = new ()
{
Id = "moveButton",
CanFocus = true,
@@ -980,8 +1003,9 @@ public class Border : Adornment
NoDecorations = true,
NoPadding = true,
ShadowStyle = ShadowStyle.None,
- Text = $"{Glyphs.Diamond}",
+ Text = $"{Glyphs.Move}",
Visible = false,
+ Data = ViewArrangement.Movable
};
Add (_moveButton);
}
@@ -990,7 +1014,7 @@ public class Border : Adornment
{
Debug.Assert (_allSizeButton is null);
- _allSizeButton = new Button
+ _allSizeButton = new ()
{
Id = "allSizeButton",
CanFocus = true,
@@ -999,10 +1023,11 @@ public class Border : Adornment
NoDecorations = true,
NoPadding = true,
ShadowStyle = ShadowStyle.None,
- Text = $"{Glyphs.Diamond}",
+ Text = $"{Glyphs.SizeBottomRight}",
X = Pos.AnchorEnd (),
Y = Pos.AnchorEnd (),
Visible = false,
+ Data = ViewArrangement.Resizable
};
Add (_allSizeButton);
}
@@ -1011,7 +1036,7 @@ public class Border : Adornment
{
Debug.Assert (_topSizeButton is null);
- _topSizeButton = new Button
+ _topSizeButton = new ()
{
Id = "topSizeButton",
CanFocus = true,
@@ -1020,10 +1045,11 @@ public class Border : Adornment
NoDecorations = true,
NoPadding = true,
ShadowStyle = ShadowStyle.None,
- Text = $"{Glyphs.Diamond}",
+ Text = $"{Glyphs.SizeVertical}",
X = Pos.Center () + Parent!.Margin.Thickness.Horizontal,
Y = 0,
Visible = false,
+ Data = ViewArrangement.TopResizable
};
Add (_topSizeButton);
}
@@ -1032,7 +1058,7 @@ public class Border : Adornment
{
Debug.Assert (_rightSizeButton is null);
- _rightSizeButton = new Button
+ _rightSizeButton = new ()
{
Id = "rightSizeButton",
CanFocus = true,
@@ -1041,10 +1067,11 @@ public class Border : Adornment
NoDecorations = true,
NoPadding = true,
ShadowStyle = ShadowStyle.None,
- Text = $"{Glyphs.Diamond}",
+ Text = $"{Glyphs.SizeHorizontal}",
X = Pos.AnchorEnd (),
- Y = Pos.Center () + Parent!.Margin.Thickness.Vertical,
+ Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
Visible = false,
+ Data = ViewArrangement.RightResizable
};
Add (_rightSizeButton);
}
@@ -1053,7 +1080,7 @@ public class Border : Adornment
{
Debug.Assert (_leftSizeButton is null);
- _leftSizeButton = new Button
+ _leftSizeButton = new ()
{
Id = "leftSizeButton",
CanFocus = true,
@@ -1062,10 +1089,11 @@ public class Border : Adornment
NoDecorations = true,
NoPadding = true,
ShadowStyle = ShadowStyle.None,
- Text = $"{Glyphs.Diamond}",
+ Text = $"{Glyphs.SizeHorizontal}",
X = 0,
- Y = Pos.Center () + Parent!.Margin.Thickness.Vertical,
+ Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
Visible = false,
+ Data = ViewArrangement.LeftResizable
};
Add (_leftSizeButton);
}
@@ -1074,7 +1102,7 @@ public class Border : Adornment
{
Debug.Assert (_bottomSizeButton is null);
- _bottomSizeButton = new Button
+ _bottomSizeButton = new ()
{
Id = "bottomSizeButton",
CanFocus = true,
@@ -1083,29 +1111,31 @@ public class Border : Adornment
NoDecorations = true,
NoPadding = true,
ShadowStyle = ShadowStyle.None,
- Text = $"{Glyphs.Diamond}",
- X = Pos.Center () + Parent!.Margin.Thickness.Horizontal,
+ Text = $"{Glyphs.SizeVertical}",
+ X = Pos.Center () + Parent!.Margin.Thickness.Horizontal / 2,
Y = Pos.AnchorEnd (),
Visible = false,
+ Data = ViewArrangement.BottomResizable
};
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;
}
+
+ _arranging = ViewArrangement.Movable;
+ CanFocus = true;
+ SetFocus ();
}
else
{
@@ -1116,55 +1146,74 @@ public class Border : Adornment
{
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;
+
+ if (_allSizeButton is { })
+ {
+ _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;
+
+ if (_allSizeButton is { })
+ {
+ _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;
+
+ if (_allSizeButton is { })
+ {
+ _allSizeButton.X = Pos.AnchorEnd ();
+ _allSizeButton.Y = 0;
+ _allSizeButton.Visible = true;
+ }
break;
-
}
}
@@ -1173,22 +1222,24 @@ public class Border : Adornment
if (arrangement == ViewArrangement.Fixed)
{
// Keyboard mode - enable nav
- CanFocus = true;
- SetFocus ();
+
}
+
return true;
}
// Hack for now
- EndArrange ();
+ EndArrangeMode ();
+
return false;
}
private void AddArrangeModeKeyBindings ()
{
- AddCommand (Command.Quit, EndArrange);
+ AddCommand (Command.Quit, EndArrangeMode);
- AddCommand (Command.Up,
+ AddCommand (
+ Command.Up,
() =>
{
if (Parent is null)
@@ -1212,7 +1263,8 @@ public class Border : Adornment
return true;
});
- AddCommand (Command.Down,
+ AddCommand (
+ Command.Down,
() =>
{
if (Parent is null)
@@ -1232,7 +1284,9 @@ public class Border : Adornment
return true;
});
- AddCommand (Command.Left,
+
+ AddCommand (
+ Command.Left,
() =>
{
if (Parent is null)
@@ -1252,10 +1306,12 @@ public class Border : Adornment
Parent!.Width = Parent.Width! - 1;
}
}
+
return true;
});
- AddCommand (Command.Right,
+ AddCommand (
+ Command.Right,
() =>
{
if (Parent is null)
@@ -1276,18 +1332,25 @@ public class Border : Adornment
return true;
});
- AddCommand (Command.Tab, () =>
- {
- AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
+ AddCommand (
+ Command.Tab,
+ () =>
+ {
+ AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
+ _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
- return true; // Always eat
- });
- AddCommand (Command.BackTab, () =>
- {
- AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop);
+ return true; // Always eat
+ });
- return true; // Always eat
- });
+ AddCommand (
+ Command.BackTab,
+ () =>
+ {
+ AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop);
+ _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+
+ return true; // Always eat
+ });
KeyBindings.Add (Key.Esc, KeyBindingScope.HotKey, Command.Quit);
KeyBindings.Add (Application.ArrangeKey, KeyBindingScope.HotKey, Command.Quit);
@@ -1313,11 +1376,11 @@ public class Border : Adornment
if (!Thickness.Contains (Frame, framePos))
{
- EndArrange ();
+ EndArrangeMode ();
}
}
- private bool? EndArrange ()
+ private bool? EndArrangeMode ()
{
// Debug.Assert (_arranging != ViewArrangement.Fixed);
_arranging = ViewArrangement.Fixed;
@@ -1390,5 +1453,4 @@ public class Border : Adornment
_dragPosition = null;
base.Dispose (disposing);
}
-
}
diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs
index 4ef00a5b7..24215eb44 100644
--- a/Terminal.Gui/View/View.Hierarchy.cs
+++ b/Terminal.Gui/View/View.Hierarchy.cs
@@ -159,14 +159,10 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
Rectangle touched = view.Frame;
- // If a view being removed is focused, it should lose focus.
- if (view.HasFocus)
- {
- view.HasFocus = false;
- }
+ bool hadFocus = view.HasFocus;
_subviews.Remove (view);
- view._superView = null; // Null this AFTER removing focus
+ view._superView = null;
SetNeedsLayout ();
SetNeedsDisplay ();
@@ -179,11 +175,20 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
}
}
- if (HasFocus)
+ if (_previouslyFocused == view)
{
- FocusDeepest (NavigationDirection.Forward, TabStop);
+ _previouslyFocused = null;
}
+ if (hadFocus)
+ {
+ // Access _hasFocus directly; don't use HasFocus because it will try to find the focused view
+ view._hasFocus = false;
+ AdvanceFocus (NavigationDirection.Forward, null);
+ }
+
+ Debug.Assert (!view.HasFocus);
+
OnRemoved (new (this, view));
return view;
diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs
index d38d5c4f3..205183fe9 100644
--- a/Terminal.Gui/View/View.Navigation.cs
+++ b/Terminal.Gui/View/View.Navigation.cs
@@ -1,5 +1,6 @@
#nullable enable
using System.Diagnostics;
+using System.Reflection.PortableExecutable;
namespace Terminal.Gui;
@@ -39,7 +40,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
// AdvanceFocus did not advance - do we wrap, or move up to the superview?
- View [] focusChain = GetSubviewFocusChain (direction, behavior);
+ View [] focusChain = GetFocusChain (direction, behavior);
if (focusChain.Length == 0)
{
@@ -52,11 +53,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
if (direction == NavigationDirection.Forward && focused == focusChain [^1] && SuperView is null)
{
// We're at the top of the focus chain. Go back down the focus chain and focus the first TabGroup
- View [] views = GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
+ View [] views = GetFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
if (views.Length > 0)
{
- View [] subViews = views [0].GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
+ View [] subViews = views [0].GetFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
if (subViews.Length > 0)
{
@@ -71,11 +72,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
if (direction == NavigationDirection.Backward && focused == focusChain [0])
{
// We're at the bottom of the focus chain
- View [] views = GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
+ View [] views = GetFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
if (views.Length > 0)
{
- View [] subViews = views [^1].GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
+ View [] subViews = views [^1].GetFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
if (subViews.Length > 0)
{
@@ -102,7 +103,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
if (SuperView is { })
{
// If we are TabStop, and we have at least one other focusable peer, move to the SuperView's chain
- if (TabStop == TabBehavior.TabStop && SuperView is { } && SuperView.GetSubviewFocusChain (direction, behavior).Length > 1)
+ if (TabStop == TabBehavior.TabStop && SuperView is { } && SuperView.GetFocusChain (direction, behavior).Length > 1)
{
return false;
}
@@ -110,7 +111,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
// TabGroup is special-cased.
if (focused?.TabStop == TabBehavior.TabGroup)
{
- if (SuperView?.GetSubviewFocusChain (direction, TabBehavior.TabGroup)?.Length > 0)
+ if (SuperView?.GetFocusChain (direction, TabBehavior.TabGroup)?.Length > 0)
{
// Our superview has a TabGroup subview; signal we couldn't move so we nav out to it
return false;
@@ -176,7 +177,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
HasFocus = false;
}
- if (_canFocus && !HasFocus && Visible && SuperView is { } && SuperView.Focused is null)
+ if (_canFocus && !HasFocus && Visible && SuperView is { Focused: null })
{
// If CanFocus is set to true and this view does not have focus, make it enter focus
SetFocus ();
@@ -211,7 +212,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
return SetFocus ();
}
- /// Gets the currently focused Subview of this view, or if nothing is focused.
+ /// Gets the currently focused Subview or Adornment of this view, or if nothing is focused.
public View? Focused
{
get
@@ -224,7 +225,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
}
// How about in Adornments?
- if (Margin is { HasFocus:true })
+ if (Margin is { HasFocus: true })
{
return Margin;
}
@@ -287,9 +288,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
{
if (Focused is null && _subviews?.Count > 0)
{
- if (_previouslyMostFocused is { })
+ if (_previouslyFocused is { })
{
- return _previouslyMostFocused.SetFocus ();
+ return _previouslyFocused.SetFocus ();
}
return false;
@@ -300,7 +301,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
private View? FindDeepestFocusableView (NavigationDirection direction, TabBehavior? behavior)
{
- View [] indicies = GetSubviewFocusChain (direction, behavior);
+ View [] indicies = GetFocusChain (direction, behavior);
foreach (View v in indicies)
{
@@ -384,9 +385,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
}
///
- /// Caches the most focused subview when this view is losing focus. This is used by .
+ /// A cache of the subview that was focused when this view last lost focus. This is used by .
///
- private View? _previouslyMostFocused;
+ private View? _previouslyFocused;
///
/// INTERNAL: Called when focus is going to change to this view. This method is called by and
@@ -469,14 +470,15 @@ public partial class View // Focus and cross-view navigation management (TabStop
if (!traversingUp)
{
- // Restore focus to the previously most focused subview in the subview-hierarchy
+ // Restore focus to the previously focused subview
if (!RestoreFocus ())
{
// Couldn't restore focus, so use Advance to navigate to the next focusable subview
if (!AdvanceFocus (NavigationDirection.Forward, null))
{
// Couldn't advance, so we're the most focused view in the application
- _previouslyMostFocused = null;
+ _previouslyFocused = null;
+
Application.Navigation?.SetFocused (this);
}
}
@@ -590,17 +592,16 @@ public partial class View // Focus and cross-view navigation management (TabStop
// If newFocusedVew is null, we need to find the view that should get focus, and SetFocus on it.
if (!traversingDown && newFocusedView is null)
{
- if (superViewOrParent?._previouslyMostFocused is { })
+ if (superViewOrParent?._previouslyFocused is { })
{
- if (superViewOrParent?._previouslyMostFocused != this)
+ Debug.Assert (!superViewOrParent._previouslyFocused.WasDisposed);
+ if (superViewOrParent._previouslyFocused != this)
{
- superViewOrParent?._previouslyMostFocused?.SetFocus ();
+ superViewOrParent?._previouslyFocused?.SetFocus ();
// The above will cause SetHasFocusFalse, so we can return
return;
}
-
- newFocusedView = superViewOrParent?._previouslyMostFocused;
}
if (superViewOrParent is { })
@@ -655,9 +656,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
a.SetHasFocusFalse (newFocusedView, true);
}
- Debug.Assert (!mostFocused.WasDisposed);
+ }
- _previouslyMostFocused = mostFocused;
+ if (superViewOrParent is { })
+ {
+ superViewOrParent._previouslyFocused = this;
}
bool previousValue = HasFocus;
@@ -665,8 +668,10 @@ public partial class View // Focus and cross-view navigation management (TabStop
// Note, can't be cancelled.
NotifyFocusChanging (HasFocus, !HasFocus, newFocusedView, this);
- // Get whatever peer has focus, if any
+ // Get whatever peer has focus, if any so we can update our superview's _previouslyMostFocused
View? focusedPeer = superViewOrParent?.Focused;
+
+ // Set HasFocus false
_hasFocus = false;
if (Application.Navigation is { })
@@ -687,12 +692,6 @@ public partial class View // Focus and cross-view navigation management (TabStop
return;
}
- if (superViewOrParent is { })
- {
- Debug.Assert(!focusedPeer.WasDisposed);
- superViewOrParent._previouslyMostFocused = focusedPeer;
- }
-
// Post-conditions - prove correctness
if (HasFocus == previousValue)
{
@@ -739,32 +738,48 @@ public partial class View // Focus and cross-view navigation management (TabStop
#region Tab/Focus Handling
///
- /// Gets TabIndexes that are scoped to the specified behavior and direction. If behavior is null, all TabIndexes are
- /// returned.
+ /// Gets the subviews and Adornments of this view that are scoped to the specified behavior and direction. If behavior is null, all focusable subviews and
+ /// Adornments are returned.
///
///
///
///
- /// GetScopedTabIndexes
- internal View [] GetSubviewFocusChain (NavigationDirection direction, TabBehavior? behavior)
+ internal View [] GetFocusChain (NavigationDirection direction, TabBehavior? behavior)
{
- IEnumerable? fitleredSubviews;
+ IEnumerable? filteredSubviews;
if (behavior.HasValue)
{
- fitleredSubviews = _subviews?.Where (v => v.TabStop == behavior && v is { CanFocus: true, Visible: true, Enabled: true });
+ filteredSubviews = _subviews?.Where (v => v.TabStop == behavior && v is { CanFocus: true, Visible: true, Enabled: true });
}
else
{
- fitleredSubviews = _subviews?.Where (v => v is { CanFocus: true, Visible: true, Enabled: true });
+ filteredSubviews = _subviews?.Where (v => v is { CanFocus: true, Visible: true, Enabled: true });
+ }
+
+
+ // How about in Adornments?
+ if (Padding is { CanFocus: true, Visible: true, Enabled: true } && Padding.TabStop == behavior)
+ {
+ filteredSubviews = filteredSubviews?.Append (Padding);
+ }
+
+ if (Border is { CanFocus: true, Visible: true, Enabled: true } && Border.TabStop == behavior)
+ {
+ filteredSubviews = filteredSubviews?.Append (Border);
+ }
+
+ if (Margin is { CanFocus: true, Visible: true, Enabled: true } && Margin.TabStop == behavior)
+ {
+ filteredSubviews = filteredSubviews?.Append (Margin);
}
if (direction == NavigationDirection.Backward)
{
- fitleredSubviews = fitleredSubviews?.Reverse ();
+ filteredSubviews = filteredSubviews?.Reverse ();
}
- return fitleredSubviews?.ToArray () ?? Array.Empty ();
+ return filteredSubviews?.ToArray () ?? Array.Empty ();
}
private TabBehavior? _tabStop;
diff --git a/UnitTests/View/Navigation/SetFocusTests.cs b/UnitTests/View/Navigation/SetFocusTests.cs
index 8dfd6375a..832c88ac3 100644
--- a/UnitTests/View/Navigation/SetFocusTests.cs
+++ b/UnitTests/View/Navigation/SetFocusTests.cs
@@ -260,6 +260,14 @@ public class SetFocusTests () : TestsAllViews
Assert.False (subViewSubView3.HasFocus);
view.Border.CanFocus = true;
+ 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.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (view.Border.HasFocus);