mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Keyboard UI for move/resize POC
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Gets or sets the key to activate arranging views using the keyboard.</summary>
|
||||
[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);
|
||||
|
||||
|
||||
@@ -267,5 +267,10 @@ public enum Command
|
||||
New,
|
||||
|
||||
/// <summary>Shows context about the item (e.g. a context menu).</summary>
|
||||
ShowContextMenu
|
||||
ShowContextMenu,
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a user interface for editing.
|
||||
/// </summary>
|
||||
Edit
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"Application.NextTabGroupKey": "F6",
|
||||
"Application.PrevTabGroupKey": "Shift+F6",
|
||||
"Application.QuitKey": "Esc",
|
||||
"Application.ArrangeKey": "Ctrl+F5",
|
||||
"Key.Separator": "+",
|
||||
|
||||
"Theme": "Default",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using static Terminal.Gui.SpinnerStyle;
|
||||
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>The Border for a <see cref="View"/>.</summary>
|
||||
@@ -52,8 +55,9 @@ public class Border : Adornment
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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<Color> stops, out List<int> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user