diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs
index 259af8fba..74411b09b 100644
--- a/Terminal.Gui/Application/Application.Keyboard.cs
+++ b/Terminal.Gui/Application/Application.Keyboard.cs
@@ -5,12 +5,10 @@ public static partial class Application // Keyboard handling
{
private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrrides
private static Key _nextTabKey = Key.Tab; // Resources/config.json overrrides
-
private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrrides
-
private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrrides
-
private static Key _quitKey = Key.Esc; // Resources/config.json overrrides
+ private static Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrrides
static Application () { AddApplicationKeyBindings (); }
@@ -262,6 +260,22 @@ public static partial class Application // Keyboard handling
}
}
+
+ /// Gets or sets the key to activate arranging views using the keyboard.
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ public static Key ArrangeKey
+ {
+ get => _arrangeKey;
+ set
+ {
+ if (_arrangeKey != value)
+ {
+ ReplaceKey (_arrangeKey, value);
+ _arrangeKey = value;
+ }
+ }
+ }
+
internal static void AddApplicationKeyBindings ()
{
CommandImplementations = new ();
@@ -344,6 +358,24 @@ public static partial class Application // Keyboard handling
}
);
+ AddCommand (Command.Edit, static () =>
+ {
+ View? viewToArrange = Navigation?.GetFocused ();
+
+ // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed
+ while (viewToArrange?.SuperView is { } && viewToArrange.Arrangement == ViewArrangement.Fixed)
+ {
+ viewToArrange = viewToArrange.SuperView;
+ }
+
+ if (viewToArrange is { })
+ {
+ return viewToArrange.Border?.Arrange ();
+ }
+
+ return false;
+ });
+
KeyBindings.Clear ();
// Resources/config.json overrrides
@@ -352,6 +384,7 @@ public static partial class Application // Keyboard handling
NextTabGroupKey = Key.F6;
PrevTabGroupKey = Key.F6.WithShift;
QuitKey = Key.Esc;
+ ArrangeKey = Key.F5.WithCtrl;
KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
@@ -365,6 +398,8 @@ public static partial class Application // Keyboard handling
KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextViewOrTop);
KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousViewOrTop);
+ KeyBindings.Add (ArrangeKey, KeyBindingScope.Application, Command.Edit);
+
// TODO: Refresh Key should be configurable
KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs
index 42dcb16e6..2eaba7c0d 100644
--- a/Terminal.Gui/Input/Command.cs
+++ b/Terminal.Gui/Input/Command.cs
@@ -267,5 +267,10 @@ public enum Command
New,
/// Shows context about the item (e.g. a context menu).
- ShowContextMenu
+ ShowContextMenu,
+
+ ///
+ /// Invokes a user interface for editing.
+ ///
+ Edit
}
diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json
index adba1e584..9be0b0212 100644
--- a/Terminal.Gui/Resources/config.json
+++ b/Terminal.Gui/Resources/config.json
@@ -22,6 +22,7 @@
"Application.NextTabGroupKey": "F6",
"Application.PrevTabGroupKey": "Shift+F6",
"Application.QuitKey": "Esc",
+ "Application.ArrangeKey": "Ctrl+F5",
"Key.Separator": "+",
"Theme": "Default",
diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs
index de6ebd4cc..05070d887 100644
--- a/Terminal.Gui/View/Adornment/Border.cs
+++ b/Terminal.Gui/View/Adornment/Border.cs
@@ -1,3 +1,6 @@
+using System.Diagnostics;
+using static Terminal.Gui.SpinnerStyle;
+
namespace Terminal.Gui;
/// The Border for a .
@@ -52,8 +55,9 @@ public class Border : Adornment
///
public Border (View parent) : base (parent)
{
- /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */
Parent = parent;
+ CanFocus = false;
+
Application.GrabbingMouse += Application_GrabbingMouse;
Application.UnGrabbingMouse += Application_UnGrabbingMouse;
@@ -70,6 +74,10 @@ public class Border : Adornment
public Button CloseButton { get; internal set; }
#endif
+
+ [CanBeNull]
+ private Button _arrangeButton;
+
///
public override void BeginInit ()
{
@@ -108,8 +116,11 @@ public class Border : Adornment
LayoutStarted += OnLayoutStarted;
}
#endif
+
}
+
+
#if SUBVIEW_BASED_BORDER
private void OnLayoutStarted (object sender, LayoutEventArgs e)
{
@@ -427,6 +438,8 @@ public class Border : Adornment
int sideLineLength = borderBounds.Height;
bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 };
+ LineStyle lineStyle = LineStyle;
+
if (Settings.FastHasFlags (BorderSettings.Title))
{
if (Thickness.Top == 2)
@@ -506,7 +519,7 @@ public class Border : Adornment
new (borderBounds.Location.X, titleY),
borderBounds.Width,
Orientation.Horizontal,
- LineStyle,
+ lineStyle,
Driver.GetAttribute ()
);
}
@@ -521,7 +534,7 @@ public class Border : Adornment
new (borderBounds.X + 1, topTitleLineY),
Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
Orientation.Horizontal,
- LineStyle,
+ lineStyle,
Driver.GetAttribute ()
);
}
@@ -535,7 +548,7 @@ public class Border : Adornment
new (borderBounds.X + 1, topTitleLineY),
Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
Orientation.Horizontal,
- LineStyle,
+ lineStyle,
Driver.GetAttribute ()
);
@@ -543,7 +556,7 @@ public class Border : Adornment
new (borderBounds.X + 1, topTitleLineY + 2),
Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
Orientation.Horizontal,
- LineStyle,
+ lineStyle,
Driver.GetAttribute ()
);
}
@@ -554,7 +567,7 @@ public class Border : Adornment
new (borderBounds.Location.X, titleY),
2,
Orientation.Horizontal,
- LineStyle,
+ lineStyle,
Driver.GetAttribute ()
);
@@ -593,7 +606,7 @@ public class Border : Adornment
),
borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
Orientation.Horizontal,
- LineStyle,
+ lineStyle,
Driver.GetAttribute ()
);
}
@@ -607,7 +620,7 @@ public class Border : Adornment
new (borderBounds.Location.X, titleY),
sideLineLength,
Orientation.Vertical,
- LineStyle,
+ lineStyle,
Driver.GetAttribute ()
);
}
@@ -619,7 +632,7 @@ public class Border : Adornment
new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
borderBounds.Width,
Orientation.Horizontal,
- LineStyle,
+ lineStyle,
Driver.GetAttribute ()
);
}
@@ -630,7 +643,7 @@ public class Border : Adornment
new (borderBounds.X + borderBounds.Width - 1, titleY),
sideLineLength,
Orientation.Vertical,
- LineStyle,
+ lineStyle,
Driver.GetAttribute ()
);
}
@@ -705,7 +718,7 @@ public class Border : Adornment
private static void GetAppealingGradientColors (out List stops, out List steps)
{
// Define the colors of the gradient stops with more appealing colors
- stops = new()
+ stops = new ()
{
new (0, 128, 255), // Bright Blue
new (0, 255, 128), // Bright Green
@@ -716,6 +729,174 @@ public class Border : Adornment
// Define the number of steps between each color for smoother transitions
// If we pass only a single value then it will assume equal steps between all pairs
- steps = new() { 15 };
+ steps = new () { 15 };
+ }
+
+ private ViewArrangement _arranging;
+
+ public bool? Arrange ()
+ {
+ Debug.Assert (_arranging == ViewArrangement.Fixed);
+
+ CanFocus = true;
+ SetFocus ();
+
+ Debug.Assert (_arrangeButton is null);
+ _arrangeButton = new Button
+ {
+ CanFocus = true,
+ Width = 1,
+ Height = 1,
+ NoDecorations = true,
+ NoPadding = true,
+ ShadowStyle = ShadowStyle.None,
+ Text = $"{Glyphs.Diamond}",
+ };
+ Add (_arrangeButton);
+
+ AddCommand (Command.QuitToplevel, EndArrange);
+
+ AddCommand (Command.LineUp,
+ () =>
+ {
+ if (_arranging == ViewArrangement.Movable)
+ {
+ Parent!.Y = Parent.Y - 1;
+ }
+
+ if (_arranging == ViewArrangement.Resizable)
+ {
+ if (Parent!.Viewport.Height > 0)
+ {
+ Parent!.Height = Parent.Height - 1;
+ }
+ }
+
+ return true;
+ });
+
+ AddCommand (Command.LineDown,
+ () =>
+ {
+ if (_arranging == ViewArrangement.Movable)
+ {
+ Parent!.Y = Parent.Y + 1;
+ }
+
+ if (_arranging == ViewArrangement.Resizable)
+ {
+ Parent!.Height = Parent.Height + 1;
+ }
+
+ return true;
+ });
+ AddCommand (Command.Left,
+ () =>
+ {
+ if (_arranging == ViewArrangement.Movable)
+ {
+ Parent!.X = Parent.X - 1;
+ }
+
+ if (_arranging == ViewArrangement.Resizable)
+ {
+ if (Parent!.Viewport.Width > 0)
+ {
+ Parent!.Width = Parent.Width - 1;
+ }
+ }
+ return true;
+ });
+
+ AddCommand (Command.Right,
+ () =>
+ {
+ if (_arranging == ViewArrangement.Movable)
+ {
+ Parent!.X = Parent.X + 1;
+ }
+
+ if (_arranging == ViewArrangement.Resizable)
+ {
+ Parent!.Width = Parent.Width + 1;
+ }
+
+ return true;
+ });
+
+ AddCommand (Command.Tab,
+ () =>
+ {
+ // TODO: Move arrangement focus to next side
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
+ {
+ _arranging = ViewArrangement.Resizable;
+ _arrangeButton.X = Pos.AnchorEnd ();
+ _arrangeButton.Y = Pos.AnchorEnd ();
+
+ return true;
+ }
+ return true;
+ });
+
+ AddCommand (Command.BackTab,
+ () =>
+ {
+ // TODO: Move arrangement focus to prev side
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+ {
+ _arranging = ViewArrangement.Movable;
+ _arrangeButton.X = 0;
+ _arrangeButton.Y = 0;
+ return true;
+ }
+ return true;
+ });
+
+ KeyBindings.Add (Key.Esc, KeyBindingScope.HotKey, Command.QuitToplevel);
+ KeyBindings.Add (Application.ArrangeKey, KeyBindingScope.HotKey, Command.QuitToplevel);
+ KeyBindings.Add (Key.CursorUp, KeyBindingScope.HotKey, Command.LineUp);
+ KeyBindings.Add (Key.CursorDown, KeyBindingScope.HotKey, Command.LineDown);
+ KeyBindings.Add (Key.CursorLeft, KeyBindingScope.HotKey, Command.Left);
+ KeyBindings.Add (Key.CursorRight, KeyBindingScope.HotKey, Command.Right);
+
+ KeyBindings.Add (Key.Tab, KeyBindingScope.HotKey, Command.Tab);
+ KeyBindings.Add (Key.Tab.WithShift, KeyBindingScope.HotKey, Command.BackTab);
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+ {
+ _arranging = ViewArrangement.Movable;
+ _arrangeButton.X = 0;
+ _arrangeButton.Y = 0;
+ return true;
+ }
+ else
+ {
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
+ {
+ _arranging = ViewArrangement.Resizable;
+ _arrangeButton.X = Pos.AnchorEnd ();
+ _arrangeButton.Y = Pos.AnchorEnd ();
+
+ return true;
+ }
+ }
+
+ // Hack for now
+ EndArrange ();
+ return false;
+ }
+ private bool? EndArrange ()
+ {
+ _arranging = ViewArrangement.Fixed;
+ CanFocus = false;
+
+ KeyBindings.Clear ();
+
+ Remove (_arrangeButton);
+ _arrangeButton.Dispose ();
+ _arrangeButton = null;
+
+ return true;
}
}
diff --git a/Terminal.Gui/View/Adornment/ShadowView.cs b/Terminal.Gui/View/Adornment/ShadowView.cs
index c9893c65c..4e1647149 100644
--- a/Terminal.Gui/View/Adornment/ShadowView.cs
+++ b/Terminal.Gui/View/Adornment/ShadowView.cs
@@ -137,7 +137,7 @@ internal class ShadowView : View
Rectangle screen = ViewportToScreen (viewport);
// Fill the rest of the rectangle
- for (int i = screen.Y; i < screen.Y + viewport.Height; i++)
+ for (int i = Math.Max (0, screen.Y); i < screen.Y + viewport.Height; i++)
{
Driver.Move (screen.X, i);
diff --git a/Terminal.Gui/View/View.Keyboard.cs b/Terminal.Gui/View/View.Keyboard.cs
index ce72d068a..9561b6a5c 100644
--- a/Terminal.Gui/View/View.Keyboard.cs
+++ b/Terminal.Gui/View/View.Keyboard.cs
@@ -579,6 +579,13 @@ public partial class View // Keyboard APIs
private bool ProcessAdornmentKeyBindings (Adornment adornment, Key keyEvent, KeyBindingScope scope, ref bool? handled)
{
+ bool? adornmentHandled = adornment.OnInvokingKeyBindings (keyEvent, scope);
+
+ if (adornmentHandled is true)
+ {
+ return true;
+ }
+
if (adornment?.Subviews is null)
{
return false;
diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs
index 852b4b130..77b9b8cbc 100644
--- a/Terminal.Gui/View/View.Navigation.cs
+++ b/Terminal.Gui/View/View.Navigation.cs
@@ -422,6 +422,20 @@ 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).
@@ -433,6 +447,15 @@ public partial class View // Focus and cross-view navigation management (TabStop
// 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;
+ }
+ }
+
_hasFocus = true;
// Ensure that the peer loses focus
diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs
index d6626659d..577c60d2b 100644
--- a/Terminal.Gui/Views/Dialog.cs
+++ b/Terminal.Gui/Views/Dialog.cs
@@ -80,17 +80,6 @@ public class Dialog : Window
Modal = true;
ButtonAlignment = DefaultButtonAlignment;
ButtonAlignmentModes = DefaultButtonAlignmentModes;
-
- AddCommand (
- Command.QuitToplevel,
- () =>
- {
- Canceled = true;
- RequestStop ();
-
- return true;
- });
- KeyBindings.Add (Key.Esc, Command.QuitToplevel);
}
// BUGBUG: We override GetNormal/FocusColor because "Dialog" ColorScheme is goofy.