mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 07:47:54 +01:00
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,3 +24,5 @@ UnitTests/TestResults
|
||||
demo.*
|
||||
|
||||
*.deb
|
||||
|
||||
*.tui/
|
||||
@@ -40,10 +40,10 @@ _The Documentation matches the most recent Nuget release from the `main` branch
|
||||
* **Cross Platform** - Windows, Mac, and Linux. Terminal drivers for Curses, [Windows Console](https://github.com/gui-cs/Terminal.Gui/issues/27), and the .NET Console mean apps will work well on both color and monochrome terminals.
|
||||
* **Keyboard and Mouse Input** - Both keyboard and mouse input are supported, including support for drag & drop.
|
||||
* **[Flexible Layout](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#layout)** - Supports both *Absolute layout* and an innovative *Computed Layout* system. *Computed Layout* makes it easy to layout controls relative to each other and enables dynamic terminal UIs.
|
||||
* **[Configuration & Themes](https://gui-cs.github.io/Terminal.Gui/articles/config.html)** - Terminal.Gui supports a rich configuration system that allows end-user customization of how the UI looks (e.g. colors) and behaves (e.g. key-bindings).
|
||||
* **Clipboard support** - Cut, Copy, and Paste of text provided through the [`Clipboard`](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.Clipboard.html) class.
|
||||
* **[Arbitrary Views](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.View.html)** - All visible UI elements are subclasses of the `View` class, and these in turn can contain an arbitrary number of sub-views.
|
||||
* **Advanced App Features** - The [Mainloop](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.MainLoop.html) supports processing events, idle handlers, timers, and monitoring file
|
||||
descriptors. Most classes are safe for threading.
|
||||
* **Advanced App Features** - The [Mainloop](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.MainLoop.html) supports processing events, idle handlers, timers, and monitoring file descriptors. Most classes are safe for threading.
|
||||
* **Reactive Extensions** - Use [reactive extensions](https://github.com/dotnet/reactive) and benefit from increased code readability, and the ability to apply the MVVM pattern and [ReactiveUI](https://www.reactiveui.net/) data bindings. See the [source code](https://github.com/gui-cs/Terminal.Gui/tree/master/ReactiveExample) of a sample app in order to learn how to achieve this.
|
||||
|
||||
## Showcase & Examples
|
||||
|
||||
@@ -1,566 +0,0 @@
|
||||
using NStack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Terminal.Gui;
|
||||
using UICatalog;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
using Console = Terminal.Gui.FakeConsole;
|
||||
|
||||
namespace UICatalog {
|
||||
public class ScenarioTests {
|
||||
readonly ITestOutputHelper output;
|
||||
|
||||
public ScenarioTests (ITestOutputHelper output)
|
||||
{
|
||||
#if DEBUG_IDISPOSABLE
|
||||
Responder.Instances.Clear ();
|
||||
#endif
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
int CreateInput (string input)
|
||||
{
|
||||
// Put a control-q in at the end
|
||||
FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo ('q', ConsoleKey.Q, shift: false, alt: false, control: true));
|
||||
foreach (var c in input.Reverse ()) {
|
||||
if (char.IsLetter (c)) {
|
||||
FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (char.ToLower (c), (ConsoleKey)char.ToUpper (c), shift: char.IsUpper (c), alt: false, control: false));
|
||||
} else {
|
||||
FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo (c, (ConsoleKey)c, shift: false, alt: false, control: false));
|
||||
}
|
||||
}
|
||||
return FakeConsole.MockKeyPresses.Count;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Should find any Scenarios which crash on load or do not respond to <see cref="Application.RequestStop()"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Run_All_Scenarios ()
|
||||
{
|
||||
List<Scenario> scenarios = Scenario.GetScenarios ();
|
||||
Assert.NotEmpty (scenarios);
|
||||
|
||||
foreach (var scenario in scenarios) {
|
||||
|
||||
output.WriteLine ($"Running Scenario '{scenario}'");
|
||||
|
||||
Func<MainLoop, bool> closeCallback = (MainLoop loop) => {
|
||||
Application.RequestStop ();
|
||||
return false;
|
||||
};
|
||||
|
||||
Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
// Close after a short period of time
|
||||
var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), closeCallback);
|
||||
|
||||
scenario.Init (Colors.Base);
|
||||
scenario.Setup ();
|
||||
scenario.Run ();
|
||||
Application.Shutdown ();
|
||||
#if DEBUG_IDISPOSABLE
|
||||
foreach (var inst in Responder.Instances) {
|
||||
Assert.True (inst.WasDisposed);
|
||||
}
|
||||
Responder.Instances.Clear ();
|
||||
#endif
|
||||
}
|
||||
#if DEBUG_IDISPOSABLE
|
||||
foreach (var inst in Responder.Instances) {
|
||||
Assert.True (inst.WasDisposed);
|
||||
}
|
||||
Responder.Instances.Clear ();
|
||||
#endif
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Run_Generic ()
|
||||
{
|
||||
List<Scenario> scenarios = Scenario.GetScenarios ();
|
||||
Assert.NotEmpty (scenarios);
|
||||
|
||||
var item = scenarios.FindIndex (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase));
|
||||
var generic = scenarios [item];
|
||||
// Setup some fake keypresses
|
||||
// Passing empty string will cause just a ctrl-q to be fired
|
||||
int stackSize = CreateInput ("");
|
||||
|
||||
Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
int iterations = 0;
|
||||
Application.Iteration = () => {
|
||||
iterations++;
|
||||
// Stop if we run out of control...
|
||||
if (iterations == 10) {
|
||||
Application.RequestStop ();
|
||||
}
|
||||
};
|
||||
|
||||
var ms = 1000;
|
||||
var abortCount = 0;
|
||||
Func<MainLoop, bool> abortCallback = (MainLoop loop) => {
|
||||
abortCount++;
|
||||
Application.RequestStop ();
|
||||
return false;
|
||||
};
|
||||
var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback);
|
||||
|
||||
Application.Top.KeyPress += (View.KeyEventEventArgs args) => {
|
||||
Assert.Equal (Key.CtrlMask | Key.Q, args.KeyEvent.Key);
|
||||
};
|
||||
|
||||
generic.Init (Colors.Base);
|
||||
generic.Setup ();
|
||||
// There is no need to call Application.Begin because Init already creates the Application.Top
|
||||
// If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run.
|
||||
//var rs = Application.Begin (Application.Top);
|
||||
generic.Run ();
|
||||
|
||||
//Application.End (rs);
|
||||
|
||||
Assert.Equal (0, abortCount);
|
||||
// # of key up events should match # of iterations
|
||||
Assert.Equal (1, iterations);
|
||||
// Using variable in the left side of Assert.Equal/NotEqual give error. Must be used literals values.
|
||||
//Assert.Equal (stackSize, iterations);
|
||||
|
||||
// Shutdown must be called to safely clean up Application if Init has been called
|
||||
Application.Shutdown ();
|
||||
|
||||
#if DEBUG_IDISPOSABLE
|
||||
foreach (var inst in Responder.Instances) {
|
||||
Assert.True (inst.WasDisposed);
|
||||
}
|
||||
Responder.Instances.Clear ();
|
||||
#endif
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Run_All_Views_Tester_Scenario ()
|
||||
{
|
||||
Window _leftPane;
|
||||
ListView _classListView;
|
||||
FrameView _hostPane;
|
||||
|
||||
Dictionary<string, Type> _viewClasses;
|
||||
View _curView = null;
|
||||
|
||||
// Settings
|
||||
FrameView _settingsPane;
|
||||
CheckBox _computedCheckBox;
|
||||
FrameView _locationFrame;
|
||||
RadioGroup _xRadioGroup;
|
||||
TextField _xText;
|
||||
int _xVal = 0;
|
||||
RadioGroup _yRadioGroup;
|
||||
TextField _yText;
|
||||
int _yVal = 0;
|
||||
|
||||
FrameView _sizeFrame;
|
||||
RadioGroup _wRadioGroup;
|
||||
TextField _wText;
|
||||
int _wVal = 0;
|
||||
RadioGroup _hRadioGroup;
|
||||
TextField _hText;
|
||||
int _hVal = 0;
|
||||
List<string> posNames = new List<String> { "Factor", "AnchorEnd", "Center", "Absolute" };
|
||||
List<string> dimNames = new List<String> { "Factor", "Fill", "Absolute" };
|
||||
|
||||
|
||||
Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
var Top = Application.Top;
|
||||
|
||||
_viewClasses = GetAllViewClassesCollection ()
|
||||
.OrderBy (t => t.Name)
|
||||
.Select (t => new KeyValuePair<string, Type> (t.Name, t))
|
||||
.ToDictionary (t => t.Key, t => t.Value);
|
||||
|
||||
_leftPane = new Window ("Classes") {
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 15,
|
||||
Height = Dim.Fill (1), // for status bar
|
||||
CanFocus = false,
|
||||
ColorScheme = Colors.TopLevel,
|
||||
};
|
||||
|
||||
_classListView = new ListView (_viewClasses.Keys.ToList ()) {
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (0),
|
||||
Height = Dim.Fill (0),
|
||||
AllowsMarking = false,
|
||||
ColorScheme = Colors.TopLevel,
|
||||
};
|
||||
_leftPane.Add (_classListView);
|
||||
|
||||
_settingsPane = new FrameView ("Settings") {
|
||||
X = Pos.Right (_leftPane),
|
||||
Y = 0, // for menu
|
||||
Width = Dim.Fill (),
|
||||
Height = 10,
|
||||
CanFocus = false,
|
||||
ColorScheme = Colors.TopLevel,
|
||||
};
|
||||
_computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 };
|
||||
_settingsPane.Add (_computedCheckBox);
|
||||
|
||||
var radioItems = new ustring [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" };
|
||||
_locationFrame = new FrameView ("Location (Pos)") {
|
||||
X = Pos.Left (_computedCheckBox),
|
||||
Y = Pos.Bottom (_computedCheckBox),
|
||||
Height = 3 + radioItems.Length,
|
||||
Width = 36,
|
||||
};
|
||||
_settingsPane.Add (_locationFrame);
|
||||
|
||||
var label = new Label ("x:") { X = 0, Y = 0 };
|
||||
_locationFrame.Add (label);
|
||||
_xRadioGroup = new RadioGroup (radioItems) {
|
||||
X = 0,
|
||||
Y = Pos.Bottom (label),
|
||||
};
|
||||
_xText = new TextField ($"{_xVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
|
||||
_locationFrame.Add (_xText);
|
||||
|
||||
_locationFrame.Add (_xRadioGroup);
|
||||
|
||||
radioItems = new ustring [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" };
|
||||
label = new Label ("y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 };
|
||||
_locationFrame.Add (label);
|
||||
_yText = new TextField ($"{_yVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
|
||||
_locationFrame.Add (_yText);
|
||||
_yRadioGroup = new RadioGroup (radioItems) {
|
||||
X = Pos.X (label),
|
||||
Y = Pos.Bottom (label),
|
||||
};
|
||||
_locationFrame.Add (_yRadioGroup);
|
||||
|
||||
_sizeFrame = new FrameView ("Size (Dim)") {
|
||||
X = Pos.Right (_locationFrame),
|
||||
Y = Pos.Y (_locationFrame),
|
||||
Height = 3 + radioItems.Length,
|
||||
Width = 40,
|
||||
};
|
||||
|
||||
radioItems = new ustring [] { "Percent(width)", "Fill(width)", "Sized(width)" };
|
||||
label = new Label ("width:") { X = 0, Y = 0 };
|
||||
_sizeFrame.Add (label);
|
||||
_wRadioGroup = new RadioGroup (radioItems) {
|
||||
X = 0,
|
||||
Y = Pos.Bottom (label),
|
||||
};
|
||||
_wText = new TextField ($"{_wVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
|
||||
_sizeFrame.Add (_wText);
|
||||
_sizeFrame.Add (_wRadioGroup);
|
||||
|
||||
radioItems = new ustring [] { "Percent(height)", "Fill(height)", "Sized(height)" };
|
||||
label = new Label ("height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 };
|
||||
_sizeFrame.Add (label);
|
||||
_hText = new TextField ($"{_hVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
|
||||
_sizeFrame.Add (_hText);
|
||||
|
||||
_hRadioGroup = new RadioGroup (radioItems) {
|
||||
X = Pos.X (label),
|
||||
Y = Pos.Bottom (label),
|
||||
};
|
||||
_sizeFrame.Add (_hRadioGroup);
|
||||
|
||||
_settingsPane.Add (_sizeFrame);
|
||||
|
||||
_hostPane = new FrameView ("") {
|
||||
X = Pos.Right (_leftPane),
|
||||
Y = Pos.Bottom (_settingsPane),
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (1), // + 1 for status bar
|
||||
ColorScheme = Colors.Dialog,
|
||||
};
|
||||
|
||||
_classListView.OpenSelectedItem += (a) => {
|
||||
_settingsPane.SetFocus ();
|
||||
};
|
||||
_classListView.SelectedItemChanged += (args) => {
|
||||
ClearClass (_curView);
|
||||
_curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]);
|
||||
};
|
||||
|
||||
_computedCheckBox.Toggled += (previousState) => {
|
||||
if (_curView != null) {
|
||||
_curView.LayoutStyle = previousState ? LayoutStyle.Absolute : LayoutStyle.Computed;
|
||||
_hostPane.LayoutSubviews ();
|
||||
}
|
||||
};
|
||||
|
||||
_xRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
|
||||
|
||||
_xText.TextChanged += (args) => {
|
||||
try {
|
||||
_xVal = int.Parse (_xText.Text.ToString ());
|
||||
DimPosChanged (_curView);
|
||||
} catch {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
_yText.TextChanged += (args) => {
|
||||
try {
|
||||
_yVal = int.Parse (_yText.Text.ToString ());
|
||||
DimPosChanged (_curView);
|
||||
} catch {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
_yRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
|
||||
|
||||
_wRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
|
||||
|
||||
_wText.TextChanged += (args) => {
|
||||
try {
|
||||
_wVal = int.Parse (_wText.Text.ToString ());
|
||||
DimPosChanged (_curView);
|
||||
} catch {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
_hText.TextChanged += (args) => {
|
||||
try {
|
||||
_hVal = int.Parse (_hText.Text.ToString ());
|
||||
DimPosChanged (_curView);
|
||||
} catch {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
_hRadioGroup.SelectedItemChanged += (selected) => DimPosChanged (_curView);
|
||||
|
||||
Top.Add (_leftPane, _settingsPane, _hostPane);
|
||||
|
||||
Top.LayoutSubviews ();
|
||||
|
||||
_curView = CreateClass (_viewClasses.First ().Value);
|
||||
|
||||
int iterations = 0;
|
||||
|
||||
Application.Iteration += () => {
|
||||
iterations++;
|
||||
|
||||
if (iterations < _viewClasses.Count) {
|
||||
_classListView.MoveDown ();
|
||||
Assert.Equal (_curView.GetType ().Name,
|
||||
_viewClasses.Values.ToArray () [_classListView.SelectedItem].Name);
|
||||
} else {
|
||||
Application.RequestStop ();
|
||||
}
|
||||
};
|
||||
|
||||
Application.Run ();
|
||||
|
||||
Assert.Equal (_viewClasses.Count, iterations);
|
||||
|
||||
Application.Shutdown ();
|
||||
|
||||
|
||||
void DimPosChanged (View view)
|
||||
{
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var layout = view.LayoutStyle;
|
||||
|
||||
try {
|
||||
view.LayoutStyle = LayoutStyle.Absolute;
|
||||
|
||||
switch (_xRadioGroup.SelectedItem) {
|
||||
case 0:
|
||||
view.X = Pos.Percent (_xVal);
|
||||
break;
|
||||
case 1:
|
||||
view.X = Pos.AnchorEnd (_xVal);
|
||||
break;
|
||||
case 2:
|
||||
view.X = Pos.Center ();
|
||||
break;
|
||||
case 3:
|
||||
view.X = Pos.At (_xVal);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (_yRadioGroup.SelectedItem) {
|
||||
case 0:
|
||||
view.Y = Pos.Percent (_yVal);
|
||||
break;
|
||||
case 1:
|
||||
view.Y = Pos.AnchorEnd (_yVal);
|
||||
break;
|
||||
case 2:
|
||||
view.Y = Pos.Center ();
|
||||
break;
|
||||
case 3:
|
||||
view.Y = Pos.At (_yVal);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (_wRadioGroup.SelectedItem) {
|
||||
case 0:
|
||||
view.Width = Dim.Percent (_wVal);
|
||||
break;
|
||||
case 1:
|
||||
view.Width = Dim.Fill (_wVal);
|
||||
break;
|
||||
case 2:
|
||||
view.Width = Dim.Sized (_wVal);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (_hRadioGroup.SelectedItem) {
|
||||
case 0:
|
||||
view.Height = Dim.Percent (_hVal);
|
||||
break;
|
||||
case 1:
|
||||
view.Height = Dim.Fill (_hVal);
|
||||
break;
|
||||
case 2:
|
||||
view.Height = Dim.Sized (_hVal);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
|
||||
} finally {
|
||||
view.LayoutStyle = layout;
|
||||
}
|
||||
UpdateTitle (view);
|
||||
}
|
||||
|
||||
void UpdateSettings (View view)
|
||||
{
|
||||
var x = view.X.ToString ();
|
||||
var y = view.Y.ToString ();
|
||||
_xRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => x.Contains (s)).First ());
|
||||
_yRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => y.Contains (s)).First ());
|
||||
_xText.Text = $"{view.Frame.X}";
|
||||
_yText.Text = $"{view.Frame.Y}";
|
||||
|
||||
var w = view.Width.ToString ();
|
||||
var h = view.Height.ToString ();
|
||||
_wRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => w.Contains (s)).First ());
|
||||
_hRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => h.Contains (s)).First ());
|
||||
_wText.Text = $"{view.Frame.Width}";
|
||||
_hText.Text = $"{view.Frame.Height}";
|
||||
}
|
||||
|
||||
void UpdateTitle (View view)
|
||||
{
|
||||
_hostPane.Title = $"{view.GetType ().Name} - {view.X.ToString ()}, {view.Y.ToString ()}, {view.Width.ToString ()}, {view.Height.ToString ()}";
|
||||
}
|
||||
|
||||
List<Type> GetAllViewClassesCollection ()
|
||||
{
|
||||
List<Type> types = new List<Type> ();
|
||||
foreach (Type type in typeof (View).Assembly.GetTypes ()
|
||||
.Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) {
|
||||
types.Add (type);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
void ClearClass (View view)
|
||||
{
|
||||
// Remove existing class, if any
|
||||
if (view != null) {
|
||||
view.LayoutComplete -= LayoutCompleteHandler;
|
||||
_hostPane.Remove (view);
|
||||
view.Dispose ();
|
||||
_hostPane.Clear ();
|
||||
}
|
||||
}
|
||||
|
||||
View CreateClass (Type type)
|
||||
{
|
||||
// If we are to create a generic Type
|
||||
if (type.IsGenericType) {
|
||||
|
||||
// For each of the <T> arguments
|
||||
List<Type> typeArguments = new List<Type> ();
|
||||
|
||||
// use <object>
|
||||
foreach (var arg in type.GetGenericArguments ()) {
|
||||
typeArguments.Add (typeof (object));
|
||||
}
|
||||
|
||||
// And change what type we are instantiating from MyClass<T> to MyClass<object>
|
||||
type = type.MakeGenericType (typeArguments.ToArray ());
|
||||
}
|
||||
// Instantiate view
|
||||
var view = (View)Activator.CreateInstance (type);
|
||||
|
||||
//_curView.X = Pos.Center ();
|
||||
//_curView.Y = Pos.Center ();
|
||||
view.Width = Dim.Percent (75);
|
||||
view.Height = Dim.Percent (75);
|
||||
|
||||
// Set the colorscheme to make it stand out if is null by default
|
||||
if (view.ColorScheme == null) {
|
||||
view.ColorScheme = Colors.Base;
|
||||
}
|
||||
|
||||
// If the view supports a Text property, set it so we have something to look at
|
||||
if (view.GetType ().GetProperty ("Text") != null) {
|
||||
try {
|
||||
view.GetType ().GetProperty ("Text")?.GetSetMethod ()?.Invoke (view, new [] { ustring.Make ("Test Text") });
|
||||
} catch (TargetInvocationException e) {
|
||||
MessageBox.ErrorQuery ("Exception", e.InnerException.Message, "Ok");
|
||||
view = null;
|
||||
}
|
||||
}
|
||||
|
||||
// If the view supports a Title property, set it so we have something to look at
|
||||
if (view != null && view.GetType ().GetProperty ("Title") != null) {
|
||||
view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { ustring.Make ("Test Title") });
|
||||
}
|
||||
|
||||
// If the view supports a Source property, set it so we have something to look at
|
||||
if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (Terminal.Gui.IListDataSource)) {
|
||||
var source = new ListWrapper (new List<ustring> () { ustring.Make ("Test Text #1"), ustring.Make ("Test Text #2"), ustring.Make ("Test Text #3") });
|
||||
view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
|
||||
}
|
||||
|
||||
// Set Settings
|
||||
_computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed;
|
||||
|
||||
// Add
|
||||
_hostPane.Add (view);
|
||||
//DimPosChanged ();
|
||||
_hostPane.LayoutSubviews ();
|
||||
_hostPane.Clear ();
|
||||
_hostPane.SetNeedsDisplay ();
|
||||
UpdateSettings (view);
|
||||
UpdateTitle (view);
|
||||
|
||||
view.LayoutComplete += LayoutCompleteHandler;
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
void LayoutCompleteHandler (View.LayoutEventArgs args)
|
||||
{
|
||||
UpdateTitle (_curView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<UseDataCollector />
|
||||
<!-- Version numbers are automatically updated by gitversion when a release is released -->
|
||||
<!-- In the source tree the version will always be 1.0 for all projects. -->
|
||||
<!-- Do not modify these. -->
|
||||
<AssemblyVersion>1.0</AssemblyVersion>
|
||||
<FileVersion>1.0</FileVersion>
|
||||
<Version>1.0</Version>
|
||||
<InformationalVersion>1.0</InformationalVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="ReportGenerator" Version="5.1.10" />
|
||||
<PackageReference Include="System.Collections" Version="4.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.2.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
|
||||
<ProjectReference Include="..\UICatalog\UICatalog.csproj" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="FineCodeCoverage">
|
||||
<Enabled>
|
||||
True
|
||||
</Enabled>
|
||||
<Exclude>
|
||||
[UICatalog]*
|
||||
</Exclude>
|
||||
<Include></Include>
|
||||
<ExcludeByFile>
|
||||
<!--**/Migrations/*
|
||||
**/Hacks/*.cs-->
|
||||
</ExcludeByFile>
|
||||
<ExcludeByAttribute>
|
||||
<!--MyCustomExcludeFromCodeCoverage-->
|
||||
</ExcludeByAttribute>
|
||||
<IncludeTestAssembly>
|
||||
False
|
||||
</IncludeTestAssembly>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
45
Terminal.Gui/Configuration/AppScope.cs
Normal file
45
Terminal.Gui/Configuration/AppScope.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
|
||||
public static partial class ConfigurationManager {
|
||||
/// <summary>
|
||||
/// The <see cref="Scope{T}"/> class for application-defined configuration settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <para>
|
||||
/// Use the <see cref="SerializableConfigurationProperty"/> attribute to mark properties that should be serialized as part
|
||||
/// of application-defined configuration settings.
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// public class MyAppSettings {
|
||||
/// [SerializableConfigurationProperty (Scope = typeof (AppScope))]
|
||||
/// public static bool? MyProperty { get; set; } = true;
|
||||
/// }
|
||||
/// </code>
|
||||
/// <para>
|
||||
/// THe resultant Json will look like this:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// "AppSettings": {
|
||||
/// "MyAppSettings.MyProperty": true,
|
||||
/// "UICatalog.ShowStatusBar": true
|
||||
/// },
|
||||
/// </code>
|
||||
/// </example>
|
||||
[JsonConverter (typeof (ScopeJsonConverter<AppScope>))]
|
||||
public class AppScope : Scope<AppScope> {
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Terminal.Gui/Configuration/AttributeJsonConverter.cs
Normal file
89
Terminal.Gui/Configuration/AttributeJsonConverter.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Terminal.Gui;
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
/// <summary>
|
||||
/// Json converter fro the <see cref="Attribute"/> class.
|
||||
/// </summary>
|
||||
public class AttributeJsonConverter : JsonConverter<Attribute> {
|
||||
private static AttributeJsonConverter instance;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static AttributeJsonConverter Instance {
|
||||
get {
|
||||
if (instance == null) {
|
||||
instance = new AttributeJsonConverter ();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartObject) {
|
||||
throw new JsonException ($"Unexpected StartObject token when parsing Attribute: {reader.TokenType}.");
|
||||
}
|
||||
|
||||
Attribute attribute = new Attribute ();
|
||||
Color foreground = (Color)(-1);
|
||||
Color background = (Color)(-1);
|
||||
while (reader.Read ()) {
|
||||
if (reader.TokenType == JsonTokenType.EndObject) {
|
||||
if (foreground == (Color)(-1) || background == (Color)(-1)) {
|
||||
throw new JsonException ($"Both Foreground and Background colors must be provided.");
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
|
||||
if (reader.TokenType != JsonTokenType.PropertyName) {
|
||||
throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
|
||||
}
|
||||
|
||||
string propertyName = reader.GetString ();
|
||||
reader.Read ();
|
||||
string color = $"\"{reader.GetString ()}\"";
|
||||
|
||||
switch (propertyName.ToLower ()) {
|
||||
case "foreground":
|
||||
foreground = JsonSerializer.Deserialize<Color> (color, options);
|
||||
break;
|
||||
case "background":
|
||||
background = JsonSerializer.Deserialize<Color> (color, options);
|
||||
break;
|
||||
//case "Bright":
|
||||
// attribute.Bright = reader.GetBoolean ();
|
||||
// break;
|
||||
//case "Underline":
|
||||
// attribute.Underline = reader.GetBoolean ();
|
||||
// break;
|
||||
//case "Reverse":
|
||||
// attribute.Reverse = reader.GetBoolean ();
|
||||
// break;
|
||||
default:
|
||||
throw new JsonException ($"Unknown Attribute property {propertyName}.");
|
||||
}
|
||||
|
||||
attribute = new Attribute (foreground, background);
|
||||
}
|
||||
throw new JsonException ();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write (Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject ();
|
||||
writer.WritePropertyName ("Foreground");
|
||||
ColorJsonConverter.Instance.Write (writer, value.Foreground, options);
|
||||
writer.WritePropertyName ("Background");
|
||||
ColorJsonConverter.Instance.Write (writer, value.Background, options);
|
||||
writer.WriteEndObject ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
76
Terminal.Gui/Configuration/ColorJsonConverter.cs
Normal file
76
Terminal.Gui/Configuration/ColorJsonConverter.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
/// <summary>
|
||||
/// Json converter for the <see cref="Color"/> class.
|
||||
/// </summary>
|
||||
public class ColorJsonConverter : JsonConverter<Color> {
|
||||
private static ColorJsonConverter instance;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton
|
||||
/// </summary>
|
||||
public static ColorJsonConverter Instance {
|
||||
get {
|
||||
if (instance == null) {
|
||||
instance = new ColorJsonConverter ();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Color Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
// Check if the value is a string
|
||||
if (reader.TokenType == JsonTokenType.String) {
|
||||
// Get the color string
|
||||
var colorString = reader.GetString ();
|
||||
|
||||
// Check if the color string is a color name
|
||||
if (Enum.TryParse (colorString, ignoreCase: true, out Color color)) {
|
||||
// Return the parsed color
|
||||
return color;
|
||||
} else {
|
||||
// Parse the color string as an RGB value
|
||||
var match = Regex.Match (colorString, @"rgb\((\d+),(\d+),(\d+)\)");
|
||||
if (match.Success) {
|
||||
var r = int.Parse (match.Groups [1].Value);
|
||||
var g = int.Parse (match.Groups [2].Value);
|
||||
var b = int.Parse (match.Groups [3].Value);
|
||||
return new TrueColor (r, g, b).ToConsoleColor ();
|
||||
} else {
|
||||
throw new JsonException ($"Invalid Color: '{colorString}'");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JsonException ($"Unexpected token when parsing Color: {reader.TokenType}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write (Utf8JsonWriter writer, Color value, JsonSerializerOptions options)
|
||||
{
|
||||
// Try to get the human readable color name from the map
|
||||
var name = Enum.GetName (typeof (Color), value);
|
||||
if (name != null) {
|
||||
// Write the color name to the JSON
|
||||
writer.WriteStringValue (name);
|
||||
} else {
|
||||
//// If the color is not in the map, look up its RGB values in the consoleDriver.colors array
|
||||
//ConsoleColor consoleColor = (ConsoleDriver [(int)value]);
|
||||
//int r = consoleColor.R;
|
||||
//int g = consoleColor.G;
|
||||
//int b = consoleColor.B;
|
||||
|
||||
//// Write the RGB values as a string to the JSON
|
||||
//writer.WriteStringValue ($"rgb({r},{g},{b})");
|
||||
throw new JsonException ($"Unknown Color value. Cannot serialize to JSON: {value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs
Normal file
89
Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
/// <summary>
|
||||
/// Implements a JSON converter for <see cref="ColorScheme"/>.
|
||||
/// </summary>
|
||||
public class ColorSchemeJsonConverter : JsonConverter<ColorScheme> {
|
||||
private static ColorSchemeJsonConverter instance;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton
|
||||
/// </summary>
|
||||
public static ColorSchemeJsonConverter Instance {
|
||||
get {
|
||||
if (instance == null) {
|
||||
instance = new ColorSchemeJsonConverter ();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ColorScheme Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartObject) {
|
||||
throw new JsonException ($"Unexpected StartObject token when parsing ColorScheme: {reader.TokenType}.");
|
||||
}
|
||||
|
||||
var colorScheme = new ColorScheme ();
|
||||
|
||||
while (reader.Read ()) {
|
||||
if (reader.TokenType == JsonTokenType.EndObject) {
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
if (reader.TokenType != JsonTokenType.PropertyName) {
|
||||
throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
|
||||
}
|
||||
|
||||
var propertyName = reader.GetString ();
|
||||
reader.Read ();
|
||||
var attribute = JsonSerializer.Deserialize<Attribute> (ref reader, options);
|
||||
|
||||
switch (propertyName.ToLower()) {
|
||||
case "normal":
|
||||
colorScheme.Normal = attribute;
|
||||
break;
|
||||
case "focus":
|
||||
colorScheme.Focus = attribute;
|
||||
break;
|
||||
case "hotnormal":
|
||||
colorScheme.HotNormal = attribute;
|
||||
break;
|
||||
case "hotfocus":
|
||||
colorScheme.HotFocus = attribute;
|
||||
break;
|
||||
case "disabled":
|
||||
colorScheme.Disabled = attribute;
|
||||
break;
|
||||
default:
|
||||
throw new JsonException ($"Unrecognized ColorScheme Attribute name: {propertyName}.");
|
||||
}
|
||||
}
|
||||
|
||||
throw new JsonException ();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write (Utf8JsonWriter writer, ColorScheme value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject ();
|
||||
|
||||
writer.WritePropertyName ("Normal");
|
||||
AttributeJsonConverter.Instance.Write (writer, value.Normal, options);
|
||||
writer.WritePropertyName ("Focus");
|
||||
AttributeJsonConverter.Instance.Write (writer, value.Focus, options);
|
||||
writer.WritePropertyName ("HotNormal");
|
||||
AttributeJsonConverter.Instance.Write (writer, value.HotNormal, options);
|
||||
writer.WritePropertyName ("HotFocus");
|
||||
AttributeJsonConverter.Instance.Write (writer, value.HotFocus, options);
|
||||
writer.WritePropertyName ("Disabled");
|
||||
AttributeJsonConverter.Instance.Write (writer, value.Disabled, options);
|
||||
|
||||
writer.WriteEndObject ();
|
||||
}
|
||||
}
|
||||
}
|
||||
663
Terminal.Gui/Configuration/ConfigurationManager.cs
Normal file
663
Terminal.Gui/Configuration/ConfigurationManager.cs
Normal file
@@ -0,0 +1,663 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
/// <summary>
|
||||
/// Provides settings and configuration management for Terminal.Gui applications.
|
||||
/// <para>
|
||||
/// Users can set Terminal.Gui settings on a global or per-application basis by providing JSON formatted configuration files.
|
||||
/// The configuration files can be placed in at <c>.tui</c> folder in the user's home directory (e.g. <c>C:/Users/username/.tui</c>,
|
||||
/// or <c>/usr/username/.tui</c>),
|
||||
/// the folder where the Terminal.Gui application was launched from (e.g. <c>./.tui</c>), or as a resource
|
||||
/// within the Terminal.Gui application's main assembly.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Settings are defined in JSON format, according to this schema:
|
||||
/// https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Settings that will apply to all applications (global settings) reside in files named <c>config.json</c>. Settings
|
||||
/// that will apply to a specific Terminal.Gui application reside in files named <c>appname.config.json</c>,
|
||||
/// where <c>appname</c> is the assembly name of the application (e.g. <c>UICatalog.config.json</c>).
|
||||
/// </para>
|
||||
/// Settings are applied using the following precedence (higher precedence settings
|
||||
/// overwrite lower precedence settings):
|
||||
/// <para>
|
||||
/// 1. Application configuration found in the users's home directory (<c>~/.tui/appname.config.json</c>) -- Highest precedence
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 2. Application configuration found in the directory the app was launched from (<c>./.tui/appname.config.json</c>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 3. Application configuration found in the applications's resources (<c>Resources/config.json</c>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 4. Global configuration found in the the user's home directory (<c>~/.tui/config.json</c>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 5. Global configuration found in the directory the app was launched from (<c>./.tui/config.json</c>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 6. Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static partial class ConfigurationManager {
|
||||
|
||||
private static readonly string _configFilename = "config.json";
|
||||
|
||||
private static readonly JsonSerializerOptions serializerOptions = new JsonSerializerOptions {
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
WriteIndented = true,
|
||||
Converters = {
|
||||
// No need to set converterss - the ConfigRootConverter uses property attributes apply the correct
|
||||
// Converter.
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// An attribute that can be applied to a property to indicate that it should included in the configuration file.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// [SerializableConfigurationProperty(Scope = typeof(Configuration.ThemeManager.ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
|
||||
/// public static BorderStyle DefaultBorderStyle {
|
||||
/// ...
|
||||
/// </example>
|
||||
[AttributeUsage (AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
public class SerializableConfigurationProperty : System.Attribute {
|
||||
/// <summary>
|
||||
/// Specifies the scope of the property.
|
||||
/// </summary>
|
||||
public Type? Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If <see langword="true"/>, the property will be serialized to the configuration file using only the property name
|
||||
/// as the key. If <see langword="false"/>, the property will be serialized to the configuration file using the
|
||||
/// property name pre-pended with the classname (e.g. <c>Application.UseSystemConsole</c>).
|
||||
/// </summary>
|
||||
public bool OmitClassName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/>
|
||||
/// to get and set the property's value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Configuration properties must be <see langword="public"/> and <see langword="static"/>
|
||||
/// and have the <see cref="SerializableConfigurationProperty"/>
|
||||
/// attribute. If the type of the property requires specialized JSON serialization,
|
||||
/// a <see cref="JsonConverter"/> must be provided using
|
||||
/// the <see cref="JsonConverterAttribute"/> attribute.
|
||||
/// </remarks>
|
||||
public class ConfigProperty {
|
||||
private object? propertyValue;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the property.
|
||||
/// </summary>
|
||||
public PropertyInfo? PropertyInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper to get either the Json property named (specified by [JsonPropertyName(name)]
|
||||
/// or the actual property name.
|
||||
/// </summary>
|
||||
/// <param name="pi"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetJsonPropertyName (PropertyInfo pi)
|
||||
{
|
||||
var jpna = pi.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
|
||||
return jpna?.Name ?? pi.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the property's value as it was either read from the class's implementation or from a config file.
|
||||
/// If the property has not been set (e.g. because no configuration file specified a value),
|
||||
/// this will be <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements of
|
||||
/// the object that are non-null).
|
||||
/// </remarks>
|
||||
public object? PropertyValue {
|
||||
get => propertyValue;
|
||||
set {
|
||||
propertyValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal object? UpdateValueFrom (object source)
|
||||
{
|
||||
if (source == null) {
|
||||
return PropertyValue;
|
||||
}
|
||||
|
||||
var ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
|
||||
if (source.GetType () != PropertyInfo!.PropertyType && (ut != null && source.GetType () != ut)) {
|
||||
throw new ArgumentException ($"The source object ({PropertyInfo!.DeclaringType}.{PropertyInfo!.Name}) is not of type {PropertyInfo!.PropertyType}.");
|
||||
}
|
||||
if (PropertyValue != null && source != null) {
|
||||
PropertyValue = DeepMemberwiseCopy (source, PropertyValue);
|
||||
} else {
|
||||
PropertyValue = source;
|
||||
}
|
||||
|
||||
return PropertyValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves (using reflection) the value of the static property described in <see cref="PropertyInfo"/>
|
||||
/// into <see cref="PropertyValue"/>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public object? RetrieveValue ()
|
||||
{
|
||||
return PropertyValue = PropertyInfo!.GetValue (null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the <see cref="PropertyValue"/> to the property described by <see cref="PropertyInfo"/>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool Apply ()
|
||||
{
|
||||
if (PropertyValue != null) {
|
||||
PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
|
||||
}
|
||||
return PropertyValue != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary of all properties in the Terminal.Gui project that are decorated with the <see cref="SerializableConfigurationProperty"/> attribute.
|
||||
/// The keys are the property names pre-pended with the class that implements the property (e.g. <c>Application.UseSystemConsole</c>).
|
||||
/// The values are instances of <see cref="ConfigProperty"/> which hold the property's value and the
|
||||
/// <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> to get and set the property's value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Is <see langword="null"/> until <see cref="Initialize"/> is called.
|
||||
/// </remarks>
|
||||
private static Dictionary<string, ConfigProperty>? _allConfigProperties;
|
||||
|
||||
/// <summary>
|
||||
/// The backing property for <see cref="Settings"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Is <see langword="null"/> until <see cref="Reset"/> is called. Gets set to a new instance by
|
||||
/// deserializtion (see <see cref="Load"/>).
|
||||
/// </remarks>
|
||||
private static SettingsScope? _settings;
|
||||
|
||||
/// <summary>
|
||||
/// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the <see cref="SettingsScope"/>
|
||||
/// attribute value.
|
||||
/// </summary>
|
||||
public static SettingsScope? Settings {
|
||||
get {
|
||||
if (_settings == null) {
|
||||
throw new InvalidOperationException ("ConfigurationManager has not been initialized. Call ConfigurationManager.Reset() before accessing the Settings property.");
|
||||
}
|
||||
return _settings;
|
||||
}
|
||||
set {
|
||||
_settings = value!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The root object of Terminal.Gui themes manager. Contains only properties with the <see cref="ThemeScope"/>
|
||||
/// attribute value.
|
||||
/// </summary>
|
||||
public static ThemeManager? Themes => ThemeManager.Instance;
|
||||
|
||||
/// <summary>
|
||||
/// Aplication-specific configuration settings scope.
|
||||
/// </summary>
|
||||
[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true), JsonPropertyName ("AppSettings")]
|
||||
public static AppScope? AppSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the internal state of ConfiguraitonManager. Nominally called once as part of application
|
||||
/// startup to initilaize global state. Also called from some Unit Tests to ensure correctness (e.g. Reset()).
|
||||
/// </summary>
|
||||
internal static void Initialize ()
|
||||
{
|
||||
_allConfigProperties = new Dictionary<string, ConfigProperty> ();
|
||||
_settings = null;
|
||||
|
||||
Dictionary<string, Type> classesWithConfigProps = new Dictionary<string, Type> (StringComparer.InvariantCultureIgnoreCase);
|
||||
// Get Terminal.Gui.dll classes
|
||||
|
||||
var types = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
|
||||
from type in assembly.GetTypes ()
|
||||
where type.GetProperties ().Any (prop => prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) != null)
|
||||
select type;
|
||||
|
||||
foreach (var classWithConfig in types) {
|
||||
classesWithConfigProps.Add (classWithConfig.Name, classWithConfig);
|
||||
}
|
||||
|
||||
Debug.WriteLine ($"ConfigManager.getConfigProperties found {classesWithConfigProps.Count} clases:");
|
||||
classesWithConfigProps.ToList ().ForEach (x => Debug.WriteLine ($" Class: {x.Key}"));
|
||||
|
||||
foreach (var p in from c in classesWithConfigProps
|
||||
let props = c.Value.GetProperties (BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Where (prop =>
|
||||
prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty)
|
||||
let enumerable = props
|
||||
from p in enumerable
|
||||
select p) {
|
||||
if (p.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty scp) {
|
||||
if (p.GetGetMethod (true)!.IsStatic) {
|
||||
// If the class name is ommited, JsonPropertyName is allowed.
|
||||
_allConfigProperties!.Add (scp.OmitClassName ? ConfigProperty.GetJsonPropertyName (p) : $"{p.DeclaringType?.Name}.{p.Name}", new ConfigProperty {
|
||||
PropertyInfo = p,
|
||||
PropertyValue = null
|
||||
});
|
||||
} else {
|
||||
throw new Exception ($"Property {p.Name} in class {p.DeclaringType?.Name} is not static. All SerializableConfigurationProperty properties must be static.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_allConfigProperties = _allConfigProperties!.OrderBy (x => x.Key).ToDictionary (x => x.Key, x => x.Value, StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
Debug.WriteLine ($"ConfigManager.Initialize found {_allConfigProperties.Count} properties:");
|
||||
_allConfigProperties.ToList ().ForEach (x => Debug.WriteLine ($" Property: {x.Key}"));
|
||||
|
||||
AppSettings = new AppScope ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a JSON document with the configuration specified.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string ToJson ()
|
||||
{
|
||||
Debug.WriteLine ($"ConfigurationManager.ToJson()");
|
||||
return JsonSerializer.Serialize<SettingsScope> (Settings!, serializerOptions);
|
||||
}
|
||||
|
||||
internal static Stream ToStream ()
|
||||
{
|
||||
var json = JsonSerializer.Serialize<SettingsScope> (Settings!, serializerOptions);
|
||||
// turn it into a stream
|
||||
var stream = new MemoryStream ();
|
||||
var writer = new StreamWriter (stream);
|
||||
writer.Write (json);
|
||||
writer.Flush ();
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for the <see cref="ConfigurationManager"/> events.
|
||||
/// </summary>
|
||||
public class ConfigurationManagerEventArgs : EventArgs {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ConfigurationManagerEventArgs"/>
|
||||
/// </summary>
|
||||
public ConfigurationManagerEventArgs ()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the <see cref="ConfigurationManager"/> should throw an exception if it encounters
|
||||
/// an error on deserialization. If <see langword="false"/> (the default), the error is logged and printed to the
|
||||
/// console when <see cref="Application.Shutdown"/> is called.
|
||||
/// </summary>
|
||||
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
|
||||
public static bool? ThrowOnJsonErrors { get; set; } = false;
|
||||
|
||||
internal static StringBuilder jsonErrors = new StringBuilder ();
|
||||
|
||||
private static void AddJsonError (string error)
|
||||
{
|
||||
Debug.WriteLine ($"ConfigurationManager: {error}");
|
||||
jsonErrors.AppendLine (error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints any Json deserialization errors that occurred during deserialization to the console.
|
||||
/// </summary>
|
||||
public static void PrintJsonErrors ()
|
||||
{
|
||||
if (jsonErrors.Length > 0) {
|
||||
Console.WriteLine ($"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:");
|
||||
Console.WriteLine (jsonErrors.ToString ());
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClearJsonErrors ()
|
||||
{
|
||||
jsonErrors.Clear ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the configuration has been updated from a configuration file. Invokes the <see cref="Updated"/>
|
||||
/// event.
|
||||
/// </summary>
|
||||
public static void OnUpdated ()
|
||||
{
|
||||
Debug.WriteLine ($"ConfigurationManager.OnApplied()");
|
||||
Updated?.Invoke (new ConfigurationManagerEventArgs ());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the configuration has been upddated from a configuration source.
|
||||
/// application.
|
||||
/// </summary>
|
||||
public static event Action<ConfigurationManagerEventArgs>? Updated;
|
||||
|
||||
/// <summary>
|
||||
/// Resets the state of <see cref="ConfigurationManager"/>. Should be called whenever a new app session
|
||||
/// (e.g. in <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/> starts. Called by <see cref="Load"/>
|
||||
/// if the <c>reset</c> parameter is <see langword="true"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// </remarks>
|
||||
public static void Reset ()
|
||||
{
|
||||
Debug.WriteLine ($"ConfigurationManager.Reset()");
|
||||
if (_allConfigProperties == null) {
|
||||
ConfigurationManager.Initialize ();
|
||||
}
|
||||
|
||||
ClearJsonErrors ();
|
||||
|
||||
Settings = new SettingsScope ();
|
||||
ThemeManager.Reset ();
|
||||
AppSettings = new AppScope ();
|
||||
|
||||
// To enable some unit tests, we only load from resources if the flag is set
|
||||
if (Locations.HasFlag (ConfigLocations.DefaultOnly)) Settings.UpdateFromResource (typeof (ConfigurationManager).Assembly, $"Terminal.Gui.Resources.{_configFilename}");
|
||||
|
||||
Apply ();
|
||||
ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply ();
|
||||
AppSettings?.Apply ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the hard coded default settings from the Terminal.Gui library implementation. Used in development of
|
||||
/// the library to generate the default configuration file. Before calling Application.Init, make sure
|
||||
/// <see cref="Locations"/> is set to <see cref="ConfigLocations.None"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is only really useful when using ConfigurationManagerTests
|
||||
/// to generate the JSON doc that is embedded into Terminal.Gui (during development).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// WARNING: The <c>Terminal.Gui.Resources.config.json</c> resource has setting defintions (Themes)
|
||||
/// that are NOT generated by this function. If you use this function to regenerate <c>Terminal.Gui.Resources.config.json</c>,
|
||||
/// make sure you copy the Theme definitions from the existing <c>Terminal.Gui.Resources.config.json</c> file.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal static void GetHardCodedDefaults ()
|
||||
{
|
||||
if (_allConfigProperties == null) {
|
||||
throw new InvalidOperationException ("Initialize must be called first.");
|
||||
}
|
||||
Settings = new SettingsScope ();
|
||||
ThemeManager.GetHardCodedDefaults ();
|
||||
AppSettings?.RetrieveValues ();
|
||||
foreach (var p in Settings!.Where (cp => cp.Value.PropertyInfo != null)) {
|
||||
Settings! [p.Key].PropertyValue = p.Value.PropertyInfo?.GetValue (null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the configuration settings to the running <see cref="Application"/> instance.
|
||||
/// </summary>
|
||||
public static void Apply ()
|
||||
{
|
||||
bool settings = Settings?.Apply () ?? false;
|
||||
bool themes = ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false;
|
||||
bool appsettings = AppSettings?.Apply () ?? false;
|
||||
if (settings || themes || appsettings) {
|
||||
OnApplied ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an updated configuration has been applied to the
|
||||
/// application. Fires the <see cref="Applied"/> event.
|
||||
/// </summary>
|
||||
public static void OnApplied ()
|
||||
{
|
||||
Debug.WriteLine ($"ConfigurationManager.OnApplied()");
|
||||
Applied?.Invoke (new ConfigurationManagerEventArgs ());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when an updated configuration has been applied to the
|
||||
/// application.
|
||||
/// </summary>
|
||||
public static event Action<ConfigurationManagerEventArgs>? Applied;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the running application. By default this property is set to the application's assembly name.
|
||||
/// </summary>
|
||||
public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the location of the configuration files. The constancts can be
|
||||
/// combined (bitwise) to specify multiple locations.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ConfigLocations {
|
||||
/// <summary>
|
||||
/// No configuration will be loaded.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used for development and testing only. For Terminal,Gui to function properly, at least
|
||||
/// <see cref="DefaultOnly"/> should be set.
|
||||
/// </remarks>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
|
||||
/// </summary>
|
||||
DefaultOnly,
|
||||
|
||||
/// <summary>
|
||||
/// This constant is a combination of all locations
|
||||
/// </summary>
|
||||
All = -1
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the locations where <see cref="ConfigurationManager"/> will look for config files.
|
||||
/// The value is <see cref="ConfigLocations.All"/>.
|
||||
/// </summary>
|
||||
public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
|
||||
|
||||
/// <summary>
|
||||
/// Loads all settings found in the various configuraiton storage locations to
|
||||
/// the <see cref="ConfigurationManager"/>. Optionally,
|
||||
/// resets all settings attributed with <see cref="SerializableConfigurationProperty"/> to the defaults
|
||||
/// defined in <see cref="LoadAppResources"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="Apply"/> to cause the loaded settings to be applied to the running application.
|
||||
/// </remarks>
|
||||
/// <param name="reset">If <see langword="true"/> the state of <see cref="ConfigurationManager"/> will
|
||||
/// be reset to the defaults defined in <see cref="LoadAppResources"/>.</param>
|
||||
public static void Load (bool reset = false)
|
||||
{
|
||||
Debug.WriteLine ($"ConfigurationManager.Load()");
|
||||
|
||||
if (reset) Reset ();
|
||||
|
||||
// LibraryResoruces is always loaded by Reset
|
||||
if (Locations == ConfigLocations.All) {
|
||||
var embeddedStylesResourceName = Assembly.GetEntryAssembly ()?
|
||||
.GetManifestResourceNames ().FirstOrDefault (x => x.EndsWith (_configFilename));
|
||||
if (string.IsNullOrEmpty(embeddedStylesResourceName)) {
|
||||
embeddedStylesResourceName = _configFilename;
|
||||
}
|
||||
|
||||
Settings = Settings?
|
||||
// Global current directory
|
||||
.Update ($"./.tui/{_configFilename}")?
|
||||
// Global home directory
|
||||
.Update ($"~/.tui/{_configFilename}")?
|
||||
// App resources
|
||||
.UpdateFromResource (Assembly.GetEntryAssembly ()!, embeddedStylesResourceName!)?
|
||||
// App current directory
|
||||
.Update ($"./.tui/{AppName}.{_configFilename}")?
|
||||
// App home directory
|
||||
.Update ($"~/.tui/{AppName}.{_configFilename}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an empty Json document with just the $schema tag.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetEmptyJson ()
|
||||
{
|
||||
var emptyScope = new SettingsScope ();
|
||||
emptyScope.Clear ();
|
||||
return JsonSerializer.Serialize<SettingsScope> (emptyScope, serializerOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// System.Text.Json does not support copying a deserialized object to an existing instance.
|
||||
/// To work around this, we implement a 'deep, memberwise copy' method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TOOD: When System.Text.Json implements `PopulateObject` revisit
|
||||
/// https://github.com/dotnet/corefx/issues/37627
|
||||
/// </remarks>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="destination"></param>
|
||||
/// <returns><paramref name="destination"/> updated from <paramref name="source"/></returns>
|
||||
internal static object? DeepMemberwiseCopy (object? source, object? destination)
|
||||
{
|
||||
if (destination == null) {
|
||||
throw new ArgumentNullException (nameof (destination));
|
||||
}
|
||||
|
||||
if (source == null) {
|
||||
return null!;
|
||||
}
|
||||
|
||||
if (source.GetType () == typeof (SettingsScope)) {
|
||||
return ((SettingsScope)destination).Update ((SettingsScope)source);
|
||||
}
|
||||
if (source.GetType () == typeof (ThemeScope)) {
|
||||
return ((ThemeScope)destination).Update ((ThemeScope)source);
|
||||
}
|
||||
if (source.GetType () == typeof (AppScope)) {
|
||||
return ((AppScope)destination).Update ((AppScope)source);
|
||||
}
|
||||
|
||||
// If value type, just use copy constructor.
|
||||
if (source.GetType ().IsValueType || source.GetType () == typeof (string)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Dictionary
|
||||
if (source.GetType ().IsGenericType && source.GetType ().GetGenericTypeDefinition ().IsAssignableFrom (typeof (Dictionary<,>))) {
|
||||
foreach (var srcKey in ((IDictionary)source).Keys) {
|
||||
if (((IDictionary)destination).Contains (srcKey))
|
||||
((IDictionary)destination) [srcKey] = DeepMemberwiseCopy (((IDictionary)source) [srcKey], ((IDictionary)destination) [srcKey]);
|
||||
else {
|
||||
((IDictionary)destination).Add (srcKey, ((IDictionary)source) [srcKey]);
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
// ALl other object types
|
||||
var sourceProps = source?.GetType ().GetProperties ().Where (x => x.CanRead).ToList ();
|
||||
var destProps = destination?.GetType ().GetProperties ().Where (x => x.CanWrite).ToList ()!;
|
||||
foreach (var (sourceProp, destProp) in
|
||||
from sourceProp in sourceProps
|
||||
where destProps.Any (x => x.Name == sourceProp.Name)
|
||||
let destProp = destProps.First (x => x.Name == sourceProp.Name)
|
||||
where destProp.CanWrite
|
||||
select (sourceProp, destProp)) {
|
||||
|
||||
var sourceVal = sourceProp.GetValue (source);
|
||||
var destVal = destProp.GetValue (destination);
|
||||
if (sourceVal != null) {
|
||||
if (destVal != null) {
|
||||
// Recurse
|
||||
destProp.SetValue (destination, DeepMemberwiseCopy (sourceVal, destVal));
|
||||
} else {
|
||||
destProp.SetValue (destination, sourceVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
return destination!;
|
||||
}
|
||||
|
||||
//public class ConfiguraitonLocation
|
||||
//{
|
||||
// public string Name { get; set; } = string.Empty;
|
||||
|
||||
// public string? Path { get; set; }
|
||||
|
||||
// public async Task<SettingsScope> UpdateAsync (Stream stream)
|
||||
// {
|
||||
// var scope = await JsonSerializer.DeserializeAsync<SettingsScope> (stream, serializerOptions);
|
||||
// if (scope != null) {
|
||||
// ConfigurationManager.Settings?.UpdateFrom (scope);
|
||||
// return scope;
|
||||
// }
|
||||
// return new SettingsScope ();
|
||||
// }
|
||||
|
||||
//}
|
||||
|
||||
//public class StreamConfiguration {
|
||||
// private bool _reset;
|
||||
|
||||
// public StreamConfiguration (bool reset)
|
||||
// {
|
||||
// _reset = reset;
|
||||
// }
|
||||
|
||||
// public StreamConfiguration UpdateAppResources ()
|
||||
// {
|
||||
// if (Locations.HasFlag (ConfigLocations.AppResources)) LoadAppResources ();
|
||||
// return this;
|
||||
// }
|
||||
|
||||
// public StreamConfiguration UpdateAppDirectory ()
|
||||
// {
|
||||
// if (Locations.HasFlag (ConfigLocations.AppDirectory)) LoadAppDirectory ();
|
||||
// return this;
|
||||
// }
|
||||
|
||||
// // Additional update methods for each location here
|
||||
|
||||
// private void LoadAppResources ()
|
||||
// {
|
||||
// // Load AppResources logic here
|
||||
// }
|
||||
|
||||
// private void LoadAppDirectory ()
|
||||
// {
|
||||
// // Load AppDirectory logic here
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
41
Terminal.Gui/Configuration/DictionaryJsonConverter.cs
Normal file
41
Terminal.Gui/Configuration/DictionaryJsonConverter.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
|
||||
class DictionaryJsonConverter<T> : JsonConverter<Dictionary<string, T>> {
|
||||
public override Dictionary<string, T> Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var dictionary = new Dictionary<string, T> ();
|
||||
while (reader.Read ()) {
|
||||
if (reader.TokenType == JsonTokenType.StartObject) {
|
||||
reader.Read ();
|
||||
if (reader.TokenType == JsonTokenType.PropertyName) {
|
||||
string key = reader.GetString ();
|
||||
reader.Read ();
|
||||
T value = JsonSerializer.Deserialize<T> (ref reader, options);
|
||||
dictionary.Add (key, value);
|
||||
}
|
||||
} else if (reader.TokenType == JsonTokenType.EndArray)
|
||||
break;
|
||||
}
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
|
||||
public override void Write (Utf8JsonWriter writer, Dictionary<string, T> value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartArray ();
|
||||
foreach (var item in value) {
|
||||
writer.WriteStartObject ();
|
||||
//writer.WriteString (item.Key, item.Key);
|
||||
writer.WritePropertyName (item.Key);
|
||||
JsonSerializer.Serialize (writer, item.Value, options);
|
||||
writer.WriteEndObject ();
|
||||
}
|
||||
writer.WriteEndArray ();
|
||||
}
|
||||
}
|
||||
}
|
||||
132
Terminal.Gui/Configuration/KeyJsonConverter.cs
Normal file
132
Terminal.Gui/Configuration/KeyJsonConverter.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
/// <summary>
|
||||
/// Json converter for the <see cref="Key"/> class.
|
||||
/// </summary>
|
||||
public class KeyJsonConverter : JsonConverter<Key> {
|
||||
/// <inheritdoc/>
|
||||
public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.StartObject) {
|
||||
Key key = Key.Unknown;
|
||||
Dictionary<string, Key> modifierDict = new Dictionary<string, Key> (comparer: StringComparer.InvariantCultureIgnoreCase) {
|
||||
{ "Shift", Key.ShiftMask },
|
||||
{ "Ctrl", Key.CtrlMask },
|
||||
{ "Alt", Key.AltMask }
|
||||
};
|
||||
|
||||
List<Key> modifiers = new List<Key> ();
|
||||
|
||||
while (reader.Read ()) {
|
||||
if (reader.TokenType == JsonTokenType.EndObject) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (reader.TokenType == JsonTokenType.PropertyName) {
|
||||
string propertyName = reader.GetString ();
|
||||
reader.Read ();
|
||||
|
||||
switch (propertyName.ToLowerInvariant ()) {
|
||||
case "key":
|
||||
if (reader.TokenType == JsonTokenType.String) {
|
||||
if (Enum.TryParse (reader.GetString (), false, out key)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The enum uses "D0..D9" for the number keys
|
||||
if (Enum.TryParse (reader.GetString ().TrimStart ('D', 'd'), false, out key)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (key == Key.Unknown || key == Key.Null) {
|
||||
throw new JsonException ($"The value \"{reader.GetString ()}\" is not a valid Key.");
|
||||
}
|
||||
|
||||
} else if (reader.TokenType == JsonTokenType.Number) {
|
||||
try {
|
||||
key = (Key)reader.GetInt32 ();
|
||||
} catch (InvalidOperationException ioe) {
|
||||
throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
|
||||
} catch (FormatException ioe) {
|
||||
throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "modifiers":
|
||||
if (reader.TokenType == JsonTokenType.StartArray) {
|
||||
while (reader.Read ()) {
|
||||
if (reader.TokenType == JsonTokenType.EndArray) {
|
||||
break;
|
||||
}
|
||||
var mod = reader.GetString ();
|
||||
try {
|
||||
modifiers.Add (modifierDict [mod]);
|
||||
} catch (KeyNotFoundException e) {
|
||||
throw new JsonException ($"The value \"{mod}\" is not a valid modifier.", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JsonException ($"Expected an array of modifiers, but got \"{reader.TokenType}\".");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JsonException ($"Unexpected Key property \"{propertyName}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var modifier in modifiers) {
|
||||
key |= modifier;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
throw new JsonException ($"Unexpected StartObject token when parsing Key: {reader.TokenType}.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write (Utf8JsonWriter writer, Key value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject ();
|
||||
|
||||
var keyName = (value & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask).ToString ();
|
||||
if (keyName != null) {
|
||||
writer.WriteString ("Key", keyName);
|
||||
} else {
|
||||
writer.WriteNumber ("Key", (uint)(value & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask));
|
||||
}
|
||||
|
||||
Dictionary<string, Key> modifierDict = new Dictionary<string, Key>
|
||||
{
|
||||
{ "Shift", Key.ShiftMask },
|
||||
{ "Ctrl", Key.CtrlMask },
|
||||
{ "Alt", Key.AltMask }
|
||||
};
|
||||
|
||||
List<string> modifiers = new List<string> ();
|
||||
foreach (var pair in modifierDict) {
|
||||
if ((value & pair.Value) == pair.Value) {
|
||||
modifiers.Add (pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
if (modifiers.Count > 0) {
|
||||
writer.WritePropertyName ("Modifiers");
|
||||
writer.WriteStartArray ();
|
||||
foreach (var modifier in modifiers) {
|
||||
writer.WriteStringValue (modifier);
|
||||
}
|
||||
writer.WriteEndArray ();
|
||||
}
|
||||
|
||||
writer.WriteEndObject ();
|
||||
}
|
||||
}
|
||||
}
|
||||
206
Terminal.Gui/Configuration/Scope.cs
Normal file
206
Terminal.Gui/Configuration/Scope.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
public static partial class ConfigurationManager {
|
||||
|
||||
/// <summary>
|
||||
/// Defines a configuration settings scope. Classes that inherit from this abstract class can be used to define
|
||||
/// scopes for configuration settings. Each scope is a JSON object that contains a set of configuration settings.
|
||||
/// </summary>
|
||||
public class Scope<T> : Dictionary<string, ConfigProperty> { //, IScope<Scope<T>> {
|
||||
/// <summary>
|
||||
/// Crates a new instance.
|
||||
/// </summary>
|
||||
public Scope () : base (StringComparer.InvariantCultureIgnoreCase)
|
||||
{
|
||||
foreach (var p in GetScopeProperties ()) {
|
||||
Add (p.Key, new ConfigProperty () { PropertyInfo = p.Value.PropertyInfo, PropertyValue = null });
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, ConfigProperty>> GetScopeProperties ()
|
||||
{
|
||||
return ConfigurationManager._allConfigProperties!.Where (cp =>
|
||||
(cp.Value.PropertyInfo?.GetCustomAttribute (typeof (SerializableConfigurationProperty))
|
||||
as SerializableConfigurationProperty)?.Scope == GetType ());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this instance from the specified source scope.
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <returns>The updated scope (this).</returns>
|
||||
public Scope<T>? Update (Scope<T> source)
|
||||
{
|
||||
foreach (var prop in source) {
|
||||
if (ContainsKey (prop.Key))
|
||||
this [prop.Key].PropertyValue = this [prop.Key].UpdateValueFrom (prop.Value.PropertyValue!);
|
||||
else {
|
||||
this [prop.Key].PropertyValue = prop.Value.PropertyValue;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the values of the properties of this scope from their corresponding static properties.
|
||||
/// </summary>
|
||||
public void RetrieveValues ()
|
||||
{
|
||||
foreach (var p in this.Where (cp => cp.Value.PropertyInfo != null)) {
|
||||
p.Value.RetrieveValue ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the values of the properties of this scope to their corresponding static properties.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal virtual bool Apply ()
|
||||
{
|
||||
bool set = false;
|
||||
foreach (var p in this.Where (t => t.Value != null && t.Value.PropertyValue != null)) {
|
||||
if (p.Value.Apply ()) {
|
||||
set = true;
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts <see cref="Scope{T}"/> instances to/from JSON. Does all the heavy lifting of reading/writing
|
||||
/// config data to/from <see cref="ConfigurationManager"/> JSON documents.
|
||||
/// </summary>
|
||||
/// <typeparam name="scopeT"></typeparam>
|
||||
public class ScopeJsonConverter<scopeT> : JsonConverter<scopeT> where scopeT : Scope<scopeT> {
|
||||
// See: https://stackoverflow.com/questions/60830084/how-to-pass-an-argument-by-reference-using-reflection
|
||||
internal abstract class ReadHelper {
|
||||
public abstract object? Read (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options);
|
||||
}
|
||||
|
||||
internal class ReadHelper<converterT> : ReadHelper {
|
||||
private readonly ReadDelegate _readDelegate;
|
||||
private delegate converterT ReadDelegate (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options);
|
||||
public ReadHelper (object converter)
|
||||
=> _readDelegate = (ReadDelegate)Delegate.CreateDelegate (typeof (ReadDelegate), converter, "Read");
|
||||
public override object? Read (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
|
||||
=> _readDelegate.Invoke (ref reader, type, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override scopeT Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartObject) {
|
||||
throw new JsonException ($"Expected a JSON object, but got \"{reader.TokenType}\".");
|
||||
}
|
||||
|
||||
var scope = (scopeT)Activator.CreateInstance (typeof (scopeT))!;
|
||||
while (reader.Read ()) {
|
||||
if (reader.TokenType == JsonTokenType.EndObject) {
|
||||
return scope!;
|
||||
}
|
||||
if (reader.TokenType != JsonTokenType.PropertyName) {
|
||||
throw new JsonException ($"Expected a JSON property name, but got \"{reader.TokenType}\".");
|
||||
}
|
||||
var propertyName = reader.GetString ();
|
||||
reader.Read ();
|
||||
|
||||
if (propertyName != null && scope!.TryGetValue (propertyName, out var configProp)) {
|
||||
// This property name was found in the Scope's ScopeProperties dictionary
|
||||
// Figure out if it needs a JsonConverter and if so, create one
|
||||
var propertyType = configProp?.PropertyInfo?.PropertyType!;
|
||||
if (configProp?.PropertyInfo?.GetCustomAttribute (typeof (JsonConverterAttribute)) is JsonConverterAttribute jca) {
|
||||
var converter = Activator.CreateInstance (jca.ConverterType!)!;
|
||||
if (converter.GetType ().BaseType == typeof (JsonConverterFactory)) {
|
||||
var factory = (JsonConverterFactory)converter;
|
||||
if (propertyType != null && factory.CanConvert (propertyType)) {
|
||||
converter = factory.CreateConverter (propertyType, options);
|
||||
}
|
||||
}
|
||||
var readHelper = Activator.CreateInstance ((Type?)typeof (ReadHelper<>).MakeGenericType (typeof (scopeT), propertyType!)!, converter) as ReadHelper;
|
||||
scope! [propertyName].PropertyValue = readHelper?.Read (ref reader, propertyType!, options);
|
||||
} else {
|
||||
scope! [propertyName].PropertyValue = JsonSerializer.Deserialize (ref reader, propertyType!, options);
|
||||
}
|
||||
} else {
|
||||
// It is not a config property. Maybe it's just a property on the Scope with [JsonInclude]
|
||||
// like ScopeSettings.$schema...
|
||||
var property = scope!.GetType ().GetProperties ().Where (p => {
|
||||
var jia = p.GetCustomAttribute (typeof (JsonIncludeAttribute)) as JsonIncludeAttribute;
|
||||
if (jia != null) {
|
||||
var jpna = p.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
|
||||
if (jpna?.Name == propertyName) {
|
||||
// Bit of a hack, modifying propertyName in an enumerator...
|
||||
propertyName = p.Name;
|
||||
return true;
|
||||
}
|
||||
|
||||
return p.Name == propertyName;
|
||||
}
|
||||
return false;
|
||||
}).FirstOrDefault ();
|
||||
|
||||
if (property != null) {
|
||||
var prop = scope.GetType ().GetProperty (propertyName!)!;
|
||||
prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, options));
|
||||
} else {
|
||||
// Unknown property
|
||||
throw new JsonException ($"Unknown property name \"{propertyName}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new JsonException ();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write (Utf8JsonWriter writer, scopeT scope, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject ();
|
||||
|
||||
var properties = scope!.GetType ().GetProperties ().Where (p => p.GetCustomAttribute (typeof (JsonIncludeAttribute)) != null);
|
||||
foreach (var p in properties) {
|
||||
writer.WritePropertyName (ConfigProperty.GetJsonPropertyName (p));
|
||||
JsonSerializer.Serialize (writer, scope.GetType ().GetProperty (p.Name)?.GetValue (scope), options);
|
||||
}
|
||||
|
||||
foreach (var p in from p in scope
|
||||
.Where (cp =>
|
||||
cp.Value.PropertyInfo?.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is
|
||||
SerializableConfigurationProperty scp && scp?.Scope == typeof (scopeT))
|
||||
where p.Value.PropertyValue != null
|
||||
select p) {
|
||||
|
||||
writer.WritePropertyName (p.Key);
|
||||
var propertyType = p.Value.PropertyInfo?.PropertyType;
|
||||
|
||||
if (propertyType != null && p.Value.PropertyInfo?.GetCustomAttribute (typeof (JsonConverterAttribute)) is JsonConverterAttribute jca) {
|
||||
var converter = Activator.CreateInstance (jca.ConverterType!)!;
|
||||
if (converter.GetType ().BaseType == typeof (JsonConverterFactory)) {
|
||||
var factory = (JsonConverterFactory)converter;
|
||||
if (factory.CanConvert (propertyType)) {
|
||||
converter = factory.CreateConverter (propertyType, options)!;
|
||||
}
|
||||
}
|
||||
if (p.Value.PropertyValue != null) {
|
||||
converter.GetType ().GetMethod ("Write")?.Invoke (converter, new object [] { writer, p.Value.PropertyValue, options });
|
||||
}
|
||||
} else {
|
||||
JsonSerializer.Serialize (writer, p.Value.PropertyValue, options);
|
||||
}
|
||||
}
|
||||
writer.WriteEndObject ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
Terminal.Gui/Configuration/SettingsScope.cs
Normal file
118
Terminal.Gui/Configuration/SettingsScope.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
public static partial class ConfigurationManager {
|
||||
/// <summary>
|
||||
/// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties
|
||||
/// attributed with <see cref="SettingsScope"/>.
|
||||
/// </summary>
|
||||
/// <example><code>
|
||||
/// {
|
||||
/// "$schema" : "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json",
|
||||
/// "Application.UseSystemConsole" : true,
|
||||
/// "Theme" : "Default",
|
||||
/// "Themes": {
|
||||
/// },
|
||||
/// },
|
||||
/// </code></example>
|
||||
/// <remarks>
|
||||
/// </remarks>
|
||||
[JsonConverter (typeof (ScopeJsonConverter<SettingsScope>))]
|
||||
public class SettingsScope : Scope<SettingsScope> {
|
||||
/// <summary>
|
||||
/// Points to our JSON schema.
|
||||
/// </summary>
|
||||
[JsonInclude, JsonPropertyName ("$schema")]
|
||||
public string Schema { get; set; } = "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json";
|
||||
|
||||
public List<string> Sources = new List<string> ();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="SettingsScope"/> with the settings in a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="stream">Json document to update the settings with.</param>
|
||||
/// <param name="source">The source (filename/resource name) the Json document was read from.</param>
|
||||
public SettingsScope? Update (Stream stream, string source)
|
||||
{
|
||||
// Update the existing settings with the new settings.
|
||||
try {
|
||||
Update (JsonSerializer.Deserialize<SettingsScope> (stream, serializerOptions)!);
|
||||
OnUpdated ();
|
||||
Debug.WriteLine ($"ConfigurationManager: Read configuration from \"{source}\"");
|
||||
Sources.Add (source);
|
||||
return this;
|
||||
} catch (JsonException e) {
|
||||
if (ThrowOnJsonErrors ?? false) {
|
||||
throw;
|
||||
} else {
|
||||
AddJsonError ($"Error deserializing {source}: {e.Message}");
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="SettingsScope"/> with the settings in a JSON file.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
public SettingsScope? Update (string filePath)
|
||||
{
|
||||
var realPath = filePath.Replace("~", Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
|
||||
if (!File.Exists (realPath)) {
|
||||
Debug.WriteLine ($"ConfigurationManager: Configuration file \"{realPath}\" does not exist.");
|
||||
Sources.Add (filePath);
|
||||
return this;
|
||||
}
|
||||
|
||||
var stream = File.OpenRead (realPath);
|
||||
return Update (stream, filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="SettingsScope"/> with the settings from a Json resource.
|
||||
/// </summary>
|
||||
/// <param name="assembly"></param>
|
||||
/// <param name="resourceName"></param>
|
||||
public SettingsScope? UpdateFromResource (Assembly assembly, string resourceName)
|
||||
{
|
||||
if (resourceName == null || string.IsNullOrEmpty (resourceName)) {
|
||||
Debug.WriteLine ($"ConfigurationManager: Resource \"{resourceName}\" does not exist in \"{assembly.GetName ().Name}\".");
|
||||
return this;
|
||||
}
|
||||
|
||||
using Stream? stream = assembly.GetManifestResourceStream (resourceName)!;
|
||||
if (stream == null) {
|
||||
Debug.WriteLine ($"ConfigurationManager: Failed to read resource \"{resourceName}\" from \"{assembly.GetName ().Name}\".");
|
||||
return this;
|
||||
}
|
||||
|
||||
return Update (stream, $"resource://[{assembly.GetName().Name}]/{resourceName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="SettingsScope"/> with the settings in a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="json">Json document to update the settings with.</param>
|
||||
/// <param name="source">The source (filename/resource name) the Json document was read from.</param>
|
||||
public SettingsScope? Update (string json, string source)
|
||||
{
|
||||
var stream = new MemoryStream ();
|
||||
var writer = new StreamWriter (stream);
|
||||
writer.Write (json);
|
||||
writer.Flush ();
|
||||
stream.Position = 0;
|
||||
|
||||
return Update (stream, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
292
Terminal.Gui/Configuration/ThemeScope.cs
Normal file
292
Terminal.Gui/Configuration/ThemeScope.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Terminal.Gui.Configuration {
|
||||
|
||||
public static partial class ConfigurationManager {
|
||||
/// <summary>
|
||||
/// The root object for a Theme. A Theme is a set of settings that are applied to the running <see cref="Application"/>
|
||||
/// as a group.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example><code>
|
||||
/// "Default": {
|
||||
/// "ColorSchemes": [
|
||||
/// {
|
||||
/// "TopLevel": {
|
||||
/// "Normal": {
|
||||
/// "Foreground": "BrightGreen",
|
||||
/// "Background": "Black"
|
||||
/// },
|
||||
/// "Focus": {
|
||||
/// "Foreground": "White",
|
||||
/// "Background": "Cyan"
|
||||
///
|
||||
/// },
|
||||
/// "HotNormal": {
|
||||
/// "Foreground": "Brown",
|
||||
/// "Background": "Black"
|
||||
///
|
||||
/// },
|
||||
/// "HotFocus": {
|
||||
/// "Foreground": "Blue",
|
||||
/// "Background": "Cyan"
|
||||
/// },
|
||||
/// "Disabled": {
|
||||
/// "Foreground": "DarkGray",
|
||||
/// "Background": "Black"
|
||||
///
|
||||
/// }
|
||||
/// }
|
||||
/// </code></example>
|
||||
[JsonConverter (typeof (ScopeJsonConverter<ThemeScope>))]
|
||||
public class ThemeScope : Scope<ThemeScope> {
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override bool Apply ()
|
||||
{
|
||||
var ret = base.Apply ();
|
||||
Application.Driver?.InitalizeColorSchemes ();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains a dictionary of the <see cref="ThemeManager.Theme"/>s for a Terminal.Gui application.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A Theme is a collection of settings that are named. The default theme is named "Default".
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The <see cref="ThemeManager.Theme"/> property is used to detemrine the currently active theme.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <para>
|
||||
/// <see cref="ThemeManager"/> is a singleton class. It is created when the first <see cref="ThemeManager"/> property is accessed.
|
||||
/// Accessing <see cref="ThemeManager.Instance"/> is the same as accessing <see cref="ConfigurationManager.Themes"/>.
|
||||
/// </para>
|
||||
/// <example><code>
|
||||
/// "Themes": [
|
||||
/// {
|
||||
/// "Default": {
|
||||
/// "ColorSchemes": [
|
||||
/// {
|
||||
/// "TopLevel": {
|
||||
/// "Normal": {
|
||||
/// "Foreground": "BrightGreen",
|
||||
/// "Background": "Black"
|
||||
/// },
|
||||
/// "Focus": {
|
||||
/// "Foreground": "White",
|
||||
/// "Background": "Cyan"
|
||||
///
|
||||
/// },
|
||||
/// "HotNormal": {
|
||||
/// "Foreground": "Brown",
|
||||
/// "Background": "Black"
|
||||
///
|
||||
/// },
|
||||
/// "HotFocus": {
|
||||
/// "Foreground": "Blue",
|
||||
/// "Background": "Cyan"
|
||||
/// },
|
||||
/// "Disabled": {
|
||||
/// "Foreground": "DarkGray",
|
||||
/// "Background": "Black"
|
||||
///
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code></example>
|
||||
public class ThemeManager : IDictionary<string, ThemeScope> {
|
||||
private static readonly ThemeManager _instance = new ThemeManager ();
|
||||
static ThemeManager () { } // Make sure it's truly lazy
|
||||
private ThemeManager () { } // Prevent instantiation outside
|
||||
|
||||
/// <summary>
|
||||
/// Class is a singleton...
|
||||
/// </summary>
|
||||
public static ThemeManager Instance { get { return _instance; } }
|
||||
|
||||
private static string theme = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected theme. This is the internal version; see <see cref="Theme"/>.
|
||||
/// </summary>
|
||||
[JsonInclude, SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true), JsonPropertyName ("Theme")]
|
||||
internal static string SelectedTheme {
|
||||
get => theme;
|
||||
set {
|
||||
var oldTheme = theme;
|
||||
theme = value;
|
||||
if (oldTheme != theme &&
|
||||
ConfigurationManager.Settings! ["Themes"]?.PropertyValue is Dictionary<string, ThemeScope> themes &&
|
||||
themes.ContainsKey (theme)) {
|
||||
ConfigurationManager.Settings! ["Theme"].PropertyValue = theme;
|
||||
Instance.OnThemeChanged (oldTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected theme. The value is persisted to the "Theme"
|
||||
/// property.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string Theme {
|
||||
get => ThemeManager.SelectedTheme;
|
||||
set {
|
||||
ThemeManager.SelectedTheme = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for the <see cref="ThemeManager"/> events.
|
||||
/// </summary>
|
||||
public class ThemeManagerEventArgs : EventArgs {
|
||||
/// <summary>
|
||||
/// The name of the new active theme..
|
||||
/// </summary>
|
||||
public string NewTheme { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ThemeManagerEventArgs"/>
|
||||
/// </summary>
|
||||
public ThemeManagerEventArgs (string newTheme)
|
||||
{
|
||||
NewTheme = newTheme;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the selected theme has changed. Fires the <see cref="ThemeChanged"/> event.
|
||||
/// </summary>
|
||||
internal void OnThemeChanged (string theme)
|
||||
{
|
||||
Debug.WriteLine ($"Themes.OnThemeChanged({theme}) -> {Theme}");
|
||||
ThemeChanged?.Invoke (new ThemeManagerEventArgs (theme));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fired he selected theme has changed.
|
||||
/// application.
|
||||
/// </summary>
|
||||
public event Action<ThemeManagerEventArgs>? ThemeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the <see cref="ThemeScope"/> definitions.
|
||||
/// </summary>
|
||||
[JsonInclude, JsonConverter (typeof (DictionaryJsonConverter<ThemeScope>))]
|
||||
[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
|
||||
public static Dictionary<string, ThemeScope>? Themes {
|
||||
get => Settings? ["Themes"]?.PropertyValue as Dictionary<string, ThemeScope>; // themes ?? new Dictionary<string, ThemeScope> ();
|
||||
set {
|
||||
//if (themes == null || value == null) {
|
||||
// themes = value;
|
||||
//} else {
|
||||
// themes = (Dictionary<string, ThemeScope>)DeepMemberwiseCopy (value!, themes!)!;
|
||||
//}
|
||||
Settings! ["Themes"].PropertyValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Reset ()
|
||||
{
|
||||
Debug.WriteLine ($"Themes.Reset()");
|
||||
|
||||
Themes?.Clear ();
|
||||
SelectedTheme = string.Empty;
|
||||
}
|
||||
|
||||
internal static void GetHardCodedDefaults ()
|
||||
{
|
||||
Debug.WriteLine ($"Themes.GetHardCodedDefaults()");
|
||||
var theme = new ThemeScope ();
|
||||
theme.RetrieveValues ();
|
||||
|
||||
Themes = new Dictionary<string, ThemeScope> (StringComparer.InvariantCultureIgnoreCase) { { "Default", theme } };
|
||||
SelectedTheme = "Default";
|
||||
}
|
||||
|
||||
#region IDictionary
|
||||
/// <inheritdoc/>
|
||||
public ICollection<string> Keys => ((IDictionary<string, ThemeScope>)Themes!).Keys;
|
||||
/// <inheritdoc/>
|
||||
public ICollection<ThemeScope> Values => ((IDictionary<string, ThemeScope>)Themes!).Values;
|
||||
/// <inheritdoc/>
|
||||
public int Count => ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Count;
|
||||
/// <inheritdoc/>
|
||||
public bool IsReadOnly => ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).IsReadOnly;
|
||||
/// <inheritdoc/>
|
||||
public ThemeScope this [string key] { get => ((IDictionary<string, ThemeScope>)Themes!) [key]; set => ((IDictionary<string, ThemeScope>)Themes!) [key] = value; }
|
||||
/// <inheritdoc/>
|
||||
public void Add (string key, ThemeScope value)
|
||||
{
|
||||
((IDictionary<string, ThemeScope>)Themes!).Add (key, value);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public bool ContainsKey (string key)
|
||||
{
|
||||
return ((IDictionary<string, ThemeScope>)Themes!).ContainsKey (key);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public bool Remove (string key)
|
||||
{
|
||||
return ((IDictionary<string, ThemeScope>)Themes!).Remove (key);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetValue (string key, out ThemeScope value)
|
||||
{
|
||||
return ((IDictionary<string, ThemeScope>)Themes!).TryGetValue (key, out value!);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void Add (KeyValuePair<string, ThemeScope> item)
|
||||
{
|
||||
((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Add (item);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void Clear ()
|
||||
{
|
||||
((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Clear ();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public bool Contains (KeyValuePair<string, ThemeScope> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Contains (item);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public void CopyTo (KeyValuePair<string, ThemeScope> [] array, int arrayIndex)
|
||||
{
|
||||
((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).CopyTo (array, arrayIndex);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public bool Remove (KeyValuePair<string, ThemeScope> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Remove (item);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<KeyValuePair<string, ThemeScope>> GetEnumerator ()
|
||||
{
|
||||
return ((IEnumerable<KeyValuePair<string, ThemeScope>>)Themes!).GetEnumerator ();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator ()
|
||||
{
|
||||
return ((IEnumerable)Themes!).GetEnumerator ();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using Terminal.Gui.Configuration;
|
||||
using System.Text.Json.Serialization;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
|
||||
@@ -117,6 +120,7 @@ namespace Terminal.Gui {
|
||||
/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
|
||||
/// </summary>
|
||||
///
|
||||
[SerializableConfigurationProperty (Scope = typeof(SettingsScope))]
|
||||
public static bool HeightAsBuffer {
|
||||
get {
|
||||
if (Driver == null) {
|
||||
@@ -139,6 +143,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
|
||||
/// </summary>
|
||||
[SerializableConfigurationProperty (Scope = typeof(SettingsScope)), JsonConverter(typeof(KeyJsonConverter))]
|
||||
public static Key AlternateForwardKey {
|
||||
get => alternateForwardKey;
|
||||
set {
|
||||
@@ -162,6 +167,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
|
||||
/// </summary>
|
||||
[SerializableConfigurationProperty (Scope = typeof(SettingsScope)), JsonConverter (typeof (KeyJsonConverter))]
|
||||
public static Key AlternateBackwardKey {
|
||||
get => alternateBackwardKey;
|
||||
set {
|
||||
@@ -185,6 +191,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets or sets the key to quit the application.
|
||||
/// </summary>
|
||||
[SerializableConfigurationProperty (Scope = typeof(SettingsScope)), JsonConverter (typeof (KeyJsonConverter))]
|
||||
public static Key QuitKey {
|
||||
get => quitKey;
|
||||
set {
|
||||
@@ -220,6 +227,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Disable or enable the mouse. The mouse is enabled by default.
|
||||
/// </summary>
|
||||
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
|
||||
public static bool IsMouseDisabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -313,6 +321,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// If <see langword="true"/>, forces the use of the System.Console-based (see <see cref="NetDriver"/>) driver. The default is <see langword="false"/>.
|
||||
/// </summary>
|
||||
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
|
||||
public static bool UseSystemConsole { get; set; } = false;
|
||||
|
||||
// For Unit testing - ignores UseSystemConsole
|
||||
@@ -392,6 +401,14 @@ namespace Terminal.Gui {
|
||||
Driver = driver;
|
||||
}
|
||||
|
||||
// Start the process of configuration management.
|
||||
// Note that we end up calling LoadConfigurationFromAllSources
|
||||
// mulitlple times. We need to do this because some settings are only
|
||||
// valid after a Driver is loaded. In this cases we need just
|
||||
// `Settings` so we can determine which driver to use.
|
||||
ConfigurationManager.Load (true);
|
||||
ConfigurationManager.Apply ();
|
||||
|
||||
if (Driver == null) {
|
||||
var p = Environment.OSVersion.Platform;
|
||||
if (ForceFakeConsole) {
|
||||
@@ -779,9 +796,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
if (mouseGrabView != null) {
|
||||
if (view == null) {
|
||||
view = mouseGrabView;
|
||||
}
|
||||
view ??= mouseGrabView;
|
||||
|
||||
var newxy = mouseGrabView.ScreenToView (me.X, me.Y);
|
||||
var nme = new MouseEvent () {
|
||||
@@ -1070,6 +1085,8 @@ namespace Terminal.Gui {
|
||||
public static void Shutdown ()
|
||||
{
|
||||
ResetState ();
|
||||
|
||||
ConfigurationManager.PrintJsonErrors ();
|
||||
}
|
||||
|
||||
// Encapsulate all setting of initial state for Application; Having
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using NStack;
|
||||
using System;
|
||||
using Terminal.Gui.Graphs;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
@@ -34,18 +35,22 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets or sets the width, in integers, of the left side of the bounding rectangle.
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public int Left;
|
||||
/// <summary>
|
||||
/// Gets or sets the width, in integers, of the upper side of the bounding rectangle.
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public int Top;
|
||||
/// <summary>
|
||||
/// Gets or sets the width, in integers, of the right side of the bounding rectangle.
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public int Right;
|
||||
/// <summary>
|
||||
/// Gets or sets the width, in integers, of the lower side of the bounding rectangle.
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public int Bottom;
|
||||
|
||||
/// <summary>
|
||||
@@ -330,6 +335,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Specifies the <see cref="Gui.BorderStyle"/> for a view.
|
||||
/// </summary>
|
||||
[JsonInclude, JsonConverter (typeof(JsonStringEnumConverter))]
|
||||
public BorderStyle BorderStyle {
|
||||
get => borderStyle;
|
||||
set {
|
||||
@@ -345,6 +351,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets or sets if a margin frame is drawn around the <see cref="Child"/> regardless the <see cref="BorderStyle"/>
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public bool DrawMarginFrame {
|
||||
get => drawMarginFrame;
|
||||
set {
|
||||
@@ -362,6 +369,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets or sets the relative <see cref="Thickness"/> of a <see cref="Border"/>.
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public Thickness BorderThickness {
|
||||
get => borderThickness;
|
||||
set {
|
||||
@@ -373,6 +381,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Color"/> that draws the outer border color.
|
||||
/// </summary>
|
||||
[JsonInclude, JsonConverter (typeof (Configuration.ColorJsonConverter))]
|
||||
public Color BorderBrush {
|
||||
get => borderBrush;
|
||||
set {
|
||||
@@ -384,6 +393,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Color"/> that fills the area between the bounds of a <see cref="Border"/>.
|
||||
/// </summary>
|
||||
[JsonInclude, JsonConverter (typeof (Configuration.ColorJsonConverter))]
|
||||
public Color Background {
|
||||
get => background;
|
||||
set {
|
||||
@@ -396,6 +406,7 @@ namespace Terminal.Gui {
|
||||
/// Gets or sets a <see cref="Thickness"/> value that describes the amount of space between a
|
||||
/// <see cref="Border"/> and its child element.
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public Thickness Padding {
|
||||
get => padding;
|
||||
set {
|
||||
@@ -407,6 +418,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets the rendered width of this element.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public int ActualWidth {
|
||||
get {
|
||||
var driver = Application.Driver;
|
||||
@@ -420,6 +432,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets the rendered height of this element.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public int ActualHeight {
|
||||
get {
|
||||
var driver = Application.Driver;
|
||||
@@ -434,21 +447,25 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets or sets the single child element of a <see cref="View"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public View Child { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent <see cref="Child"/> parent if any.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public View Parent { get => Child?.SuperView; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or private sets by the <see cref="ToplevelContainer"/>
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ToplevelContainer ChildContainer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 3D effect around the <see cref="Border"/>.
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public bool Effect3D {
|
||||
get => effect3D;
|
||||
set {
|
||||
@@ -460,6 +477,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Get or sets the offset start position for the <see cref="Effect3D"/>
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public Point Effect3DOffset {
|
||||
get => effect3DOffset;
|
||||
set {
|
||||
@@ -470,8 +488,16 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets or sets the color for the <see cref="Border"/>
|
||||
/// </summary>
|
||||
[JsonInclude, JsonConverter (typeof (Configuration.AttributeJsonConverter))]
|
||||
public Attribute? Effect3DBrush {
|
||||
get => effect3DBrush;
|
||||
get {
|
||||
if (effect3DBrush == null && effect3D) {
|
||||
return effect3DBrush = new Attribute (Color.Gray, Color.DarkGray);
|
||||
} else {
|
||||
return effect3DBrush;
|
||||
}
|
||||
}
|
||||
|
||||
set {
|
||||
effect3DBrush = value;
|
||||
OnBorderChanged ();
|
||||
@@ -481,6 +507,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// The title to be displayed for this view.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ustring Title {
|
||||
get => title;
|
||||
set {
|
||||
@@ -551,7 +578,7 @@ namespace Terminal.Gui {
|
||||
|
||||
// Draw 3D effects
|
||||
if (Effect3D) {
|
||||
driver.SetAttribute (GetEffect3DBrush ());
|
||||
driver.SetAttribute ((Attribute)Effect3DBrush);
|
||||
|
||||
var effectBorder = new Rect () {
|
||||
X = borderRect.X + Effect3DOffset.X,
|
||||
@@ -740,7 +767,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
if (Effect3D) {
|
||||
driver.SetAttribute (GetEffect3DBrush ());
|
||||
driver.SetAttribute ((Attribute)Effect3DBrush);
|
||||
|
||||
// Draw the upper Effect3D
|
||||
for (int r = frame.Y - drawMarginFrame - sumThickness.Top + effect3DOffset.Y;
|
||||
@@ -895,7 +922,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
if (Effect3D) {
|
||||
driver.SetAttribute (GetEffect3DBrush ());
|
||||
driver.SetAttribute ((Attribute)Effect3DBrush);
|
||||
|
||||
// Draw the upper Effect3D
|
||||
for (int r = Math.Max (frame.Y + effect3DOffset.Y, 0);
|
||||
@@ -940,13 +967,6 @@ namespace Terminal.Gui {
|
||||
driver.SetAttribute (savedAttribute);
|
||||
}
|
||||
|
||||
private Attribute GetEffect3DBrush ()
|
||||
{
|
||||
return Effect3DBrush == null
|
||||
? new Attribute (Color.Gray, Color.DarkGray)
|
||||
: (Attribute)Effect3DBrush;
|
||||
}
|
||||
|
||||
private void AddRuneAt (ConsoleDriver driver, int col, int row, Rune ch)
|
||||
{
|
||||
if (col < driver.Cols && row < driver.Rows && col > 0 && driver.Contents [row, col, 2] == 0
|
||||
|
||||
@@ -8,7 +8,10 @@ using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Terminal.Gui.Configuration;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
@@ -17,6 +20,7 @@ namespace Terminal.Gui {
|
||||
/// <remarks>
|
||||
/// The <see cref="Attribute.HasValidColors"/> value indicates either no-color has been set or the color is invalid.
|
||||
/// </remarks>
|
||||
[JsonConverter (typeof (ColorJsonConverter))]
|
||||
public enum Color {
|
||||
/// <summary>
|
||||
/// The black color.
|
||||
@@ -170,22 +174,26 @@ namespace Terminal.Gui {
|
||||
/// They encode both the foreground and the background color and are used in the <see cref="ColorScheme"/>
|
||||
/// class to define color schemes that can be used in an application.
|
||||
/// </remarks>
|
||||
[JsonConverter (typeof (AttributeJsonConverter))]
|
||||
public struct Attribute {
|
||||
/// <summary>
|
||||
/// The <see cref="ConsoleDriver"/>-specific color attribute value. If <see cref="Initialized"/> is <see langword="false"/>
|
||||
/// the value of this property is invalid (typically because the Attribute was created before a driver was loaded)
|
||||
/// and the attribute should be re-made (see <see cref="Make(Color, Color)"/>) before it is used.
|
||||
/// </summary>
|
||||
[JsonIgnore (Condition = JsonIgnoreCondition.Always)]
|
||||
public int Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The foreground color.
|
||||
/// </summary>
|
||||
[JsonConverter (typeof (Configuration.ColorJsonConverter))]
|
||||
public Color Foreground { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The background color.
|
||||
/// </summary>
|
||||
[JsonConverter (typeof (Configuration.ColorJsonConverter))]
|
||||
public Color Background { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -303,12 +311,14 @@ namespace Terminal.Gui {
|
||||
/// <remarks>
|
||||
/// Attributes that have not been initialized must eventually be initialized before being passed to a driver.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public bool Initialized { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the Attribute is valid (both foreground and background have valid color values).
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[JsonIgnore]
|
||||
public bool HasValidColors { get => (int)Foreground > -1 && (int)Background > -1; }
|
||||
}
|
||||
|
||||
@@ -320,6 +330,7 @@ namespace Terminal.Gui {
|
||||
/// <remarks>
|
||||
/// See also: <see cref="Colors.ColorSchemes"/>.
|
||||
/// </remarks>
|
||||
[JsonConverter (typeof (ColorSchemeJsonConverter))]
|
||||
public class ColorScheme : IEquatable<ColorScheme> {
|
||||
Attribute _normal = new Attribute (Color.White, Color.Black);
|
||||
Attribute _focus = new Attribute (Color.White, Color.Black);
|
||||
@@ -586,6 +597,8 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Provides the defined <see cref="ColorScheme"/>s.
|
||||
/// </summary>
|
||||
[SerializableConfigurationProperty (Scope = typeof(ThemeScope), OmitClassName = true)]
|
||||
[JsonConverter(typeof(DictionaryJsonConverter<ColorScheme>))]
|
||||
public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; }
|
||||
}
|
||||
|
||||
@@ -1463,6 +1476,10 @@ namespace Terminal.Gui {
|
||||
/// Ensures all <see cref="Attribute"/>s in <see cref="Colors.ColorSchemes"/> are correctly
|
||||
/// initialized by the driver.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method was previsouly named CreateColors. It was reanmed to InitalizeColorSchemes when
|
||||
/// <see cref="ConfigurationManager"/> was enabled.
|
||||
/// </remarks>
|
||||
/// <param name="supportsColors">Flag indicating if colors are supported (not used).</param>
|
||||
public void InitalizeColorSchemes (bool supportsColors = true)
|
||||
{
|
||||
@@ -1475,38 +1492,6 @@ namespace Terminal.Gui {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Define the default color theme only if the user has not defined one.
|
||||
|
||||
Colors.TopLevel.Normal = MakeColor (Color.BrightGreen, Color.Black);
|
||||
Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan);
|
||||
Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black);
|
||||
Colors.TopLevel.HotFocus = MakeColor (Color.Blue, Color.Cyan);
|
||||
Colors.TopLevel.Disabled = MakeColor (Color.DarkGray, Color.Black);
|
||||
|
||||
Colors.Base.Normal = MakeColor (Color.White, Color.Blue);
|
||||
Colors.Base.Focus = MakeColor (Color.Black, Color.Gray);
|
||||
Colors.Base.HotNormal = MakeColor (Color.BrightCyan, Color.Blue);
|
||||
Colors.Base.HotFocus = MakeColor (Color.BrightBlue, Color.Gray);
|
||||
Colors.Base.Disabled = MakeColor (Color.DarkGray, Color.Blue);
|
||||
|
||||
Colors.Dialog.Normal = MakeColor (Color.Black, Color.Gray);
|
||||
Colors.Dialog.Focus = MakeColor (Color.White, Color.DarkGray);
|
||||
Colors.Dialog.HotNormal = MakeColor (Color.Blue, Color.Gray);
|
||||
Colors.Dialog.HotFocus = MakeColor (Color.BrightYellow, Color.DarkGray);
|
||||
Colors.Dialog.Disabled = MakeColor (Color.Gray, Color.DarkGray);
|
||||
|
||||
Colors.Menu.Normal = MakeColor (Color.White, Color.DarkGray);
|
||||
Colors.Menu.Focus = MakeColor (Color.White, Color.Black);
|
||||
Colors.Menu.HotNormal = MakeColor (Color.BrightYellow, Color.DarkGray);
|
||||
Colors.Menu.HotFocus = MakeColor (Color.BrightYellow, Color.Black);
|
||||
Colors.Menu.Disabled = MakeColor (Color.Gray, Color.DarkGray);
|
||||
|
||||
Colors.Error.Normal = MakeColor (Color.Red, Color.White);
|
||||
Colors.Error.Focus = MakeColor (Color.Black, Color.BrightRed);
|
||||
Colors.Error.HotNormal = MakeColor (Color.Black, Color.White);
|
||||
Colors.Error.HotFocus = MakeColor (Color.White, Color.BrightRed);
|
||||
Colors.Error.Disabled = MakeColor (Color.DarkGray, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text.Json.Serialization;
|
||||
using NStack;
|
||||
using Terminal.Gui.Configuration;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
@@ -170,6 +173,15 @@ namespace Terminal.Gui {
|
||||
Initialize (title, Rect.Empty, padding, border);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default <see cref="BorderStyle"/> for <see cref="FrameView"/>. The default is <see cref="BorderStyle.Single"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property can be set in a Theme to change the default <see cref="BorderStyle"/> for all <see cref="Window"/>s.
|
||||
/// </remarks>
|
||||
///[SerializableConfigurationProperty (Scope = typeof (ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
|
||||
public static BorderStyle DefaultBorderStyle { get; set; } = BorderStyle.Single;
|
||||
|
||||
void Initialize (ustring title, Rect frame, int padding = 0, Border border = null)
|
||||
{
|
||||
CanFocus = true;
|
||||
@@ -178,7 +190,7 @@ namespace Terminal.Gui {
|
||||
Title = title;
|
||||
if (border == null) {
|
||||
Border = new Border () {
|
||||
BorderStyle = BorderStyle.Single,
|
||||
BorderStyle = DefaultBorderStyle,
|
||||
Padding = new Thickness (padding),
|
||||
BorderBrush = ColorScheme.Normal.Background
|
||||
};
|
||||
@@ -338,7 +350,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="EventArgs"/> which allows passing a cancelable new <see cref="Title"/> value event.
|
||||
/// Event arguments for <see cref="Title"/> chane events.
|
||||
/// </summary>
|
||||
public class TitleEventArgs : EventArgs {
|
||||
/// <summary>
|
||||
|
||||
445
Terminal.Gui/Resources/config.json
Normal file
445
Terminal.Gui/Resources/config.json
Normal file
@@ -0,0 +1,445 @@
|
||||
{
|
||||
// This document specifies the "source of truth" for default values for all Terminal.GUi settings managed by
|
||||
// ConfigurationManager. It is automatically loaded, and applied, each time Application.Init
|
||||
// is run (via the ConfiguraitonManager.Reset method).
|
||||
//
|
||||
// In otherwords, initial values set in the the codebase are always overwritten by the contents of this
|
||||
// file.
|
||||
//
|
||||
// The Unit Test method "TestConfigurationManagerSaveDefaults" can be used to re-create the base of this file, but
|
||||
// note that not all values here will be recreated (e.g. the Light and Dark themes and any property initialized
|
||||
// null.
|
||||
//
|
||||
"$schema": "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json",
|
||||
"Application.AlternateBackwardKey": {
|
||||
"Key": "PageUp",
|
||||
"Modifiers": [
|
||||
"Ctrl"
|
||||
]
|
||||
},
|
||||
"Application.AlternateForwardKey": {
|
||||
"Key": "PageDown",
|
||||
"Modifiers": [
|
||||
"Ctrl"
|
||||
]
|
||||
},
|
||||
"Application.HeightAsBuffer": false,
|
||||
"Application.QuitKey": {
|
||||
"Key": "Q",
|
||||
"Modifiers": [
|
||||
"Ctrl"
|
||||
]
|
||||
},
|
||||
"Application.UseSystemConsole": false,
|
||||
"Application.IsMouseDisabled": false,
|
||||
"Theme": "Default",
|
||||
"Themes": [
|
||||
{
|
||||
"Default": {
|
||||
"Dialog.DefaultBorder": {
|
||||
"BorderStyle": "Single",
|
||||
"DrawMarginFrame": true,
|
||||
"BorderThickness": {
|
||||
"Left": 0,
|
||||
"Top": 0,
|
||||
"Right": 0,
|
||||
"Bottom": 0
|
||||
},
|
||||
"BorderBrush": "Black",
|
||||
"Background": "Black",
|
||||
"Padding": {
|
||||
"Left": 0,
|
||||
"Top": 0,
|
||||
"Right": 0,
|
||||
"Bottom": 0
|
||||
},
|
||||
"Effect3D": true,
|
||||
"Effect3DOffset": {
|
||||
"X": 1,
|
||||
"Y": 1
|
||||
},
|
||||
"Effect3DBrush": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "DarkGray"
|
||||
}
|
||||
},
|
||||
"Dialog.DefaultButtonAlignment": "Center",
|
||||
"FrameView.DefaultBorderStyle": "Single",
|
||||
"ColorSchemes": [
|
||||
{
|
||||
"TopLevel": {
|
||||
"Normal": {
|
||||
"Foreground": "BrightGreen",
|
||||
"Background": "Black"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "White",
|
||||
"Background": "Cyan"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Brown",
|
||||
"Background": "Black"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Blue",
|
||||
"Background": "Cyan"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "Black"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Base": {
|
||||
"Normal": {
|
||||
"Foreground": "White",
|
||||
"Background": "Blue"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "BrightCyan",
|
||||
"Background": "Blue"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "BrightBlue",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "Blue"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Dialog": {
|
||||
"Normal": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "White",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Blue",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "BrightYellow",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "DarkGray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Menu": {
|
||||
"Normal": {
|
||||
"Foreground": "White",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "White",
|
||||
"Background": "Black"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "BrightYellow",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "BrightYellow",
|
||||
"Background": "Black"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "DarkGray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Error": {
|
||||
"Normal": {
|
||||
"Foreground": "Red",
|
||||
"Background": "White"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Black",
|
||||
"Background": "BrightRed"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Black",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "White",
|
||||
"Background": "BrightRed"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "White"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Dark": {
|
||||
"ColorSchemes": [
|
||||
{
|
||||
"TopLevel": {
|
||||
"Normal": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "Black"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "White",
|
||||
"Background": "BrightGreen"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "BrightGreen",
|
||||
"Background": "Black"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Cyan",
|
||||
"Background": "Black"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Base": {
|
||||
"Normal": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "Black"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "White",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "BrightYellow",
|
||||
"Background": "Black"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Cyan",
|
||||
"Background": "Black"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Dialog": {
|
||||
"Normal": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "Black"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "BrightCyan",
|
||||
"Background": "Black"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "White",
|
||||
"Background": "Black"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "White",
|
||||
"Background": "Black"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Menu": {
|
||||
"Normal": {
|
||||
"Foreground": "White",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "White",
|
||||
"Background": "Black"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "White",
|
||||
"Background": "Black"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "Black"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Error": {
|
||||
"Normal": {
|
||||
"Foreground": "BrightYellow",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "BrightYellow"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "BrightYellow",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Red",
|
||||
"Background": "BrightYellow"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Light": {
|
||||
"ColorSchemes": [
|
||||
{
|
||||
"TopLevel": {
|
||||
"Normal": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "White"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Black",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "BrightGreen",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Cyan",
|
||||
"Background": "White"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "White"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Base": {
|
||||
"Normal": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "White"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "BrightRed",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Red",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Cyan",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Dialog": {
|
||||
"Normal": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Blue",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "BrightBlue",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Menu": {
|
||||
"Normal": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "White"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "BrightRed",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "BrightRed",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "White"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Error": {
|
||||
"Normal": {
|
||||
"Foreground": "BrightYellow",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "BrightYellow"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "BrightYellow",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Red",
|
||||
"Background": "BrightYellow"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -15,6 +15,12 @@
|
||||
<Version>1.0</Version>
|
||||
<InformationalVersion>1.0</InformationalVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\config.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\config.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
|
||||
@@ -53,11 +59,13 @@
|
||||
<!-- Enable Nuget Source Link for github -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="System.Text.Json" Version="7.0.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net472;netstandard2.0;net6.0</TargetFrameworks>
|
||||
<RootNamespace>Terminal.Gui</RootNamespace>
|
||||
<AssemblyName>Terminal.Gui</AssemblyName>
|
||||
<LangVersion>8</LangVersion>
|
||||
<DocumentationFile>bin\Release\Terminal.Gui.xml</DocumentationFile>
|
||||
<GenerateDocumentationFile Condition=" '$(Configuration)' == 'Release' ">true</GenerateDocumentationFile>
|
||||
<!--<GeneratePackageOnBuild Condition=" '$(Configuration)' == 'Release' ">true</GeneratePackageOnBuild>-->
|
||||
@@ -83,4 +91,5 @@
|
||||
See: https://github.com/gui-cs/Terminal.Gui/releases
|
||||
</PackageReleaseNotes>
|
||||
</PropertyGroup>
|
||||
<ProjectExtensions><VisualStudio><UserProperties resources_4config_1json__JsonSchema="" /></VisualStudio></ProjectExtensions>
|
||||
</Project>
|
||||
@@ -21,11 +21,13 @@ namespace Terminal.Gui
|
||||
/// <summary>
|
||||
/// Gets or sets the x-coordinate of this Point.
|
||||
/// </summary>
|
||||
[System.Text.Json.Serialization.JsonInclude]
|
||||
public int X;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the y-coordinate of this Point.
|
||||
/// </summary>
|
||||
[System.Text.Json.Serialization.JsonInclude]
|
||||
public int Y;
|
||||
|
||||
// -----------------------
|
||||
@@ -159,6 +161,7 @@ namespace Terminal.Gui
|
||||
/// <remarks>
|
||||
/// Indicates if both X and Y are zero.
|
||||
/// </remarks>
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public bool IsEmpty {
|
||||
get {
|
||||
return ((X == 0) && (Y == 0));
|
||||
|
||||
@@ -11,15 +11,51 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using NStack;
|
||||
using Terminal.Gui.Graphs;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
|
||||
/// <summary>
|
||||
/// The FrameView is a container frame that draws a frame around the contents. It is similar to
|
||||
/// a GroupBox in Windows.
|
||||
/// </summary>
|
||||
public class FrameView : View {
|
||||
|
||||
//internal class FrameViewConfig : Configuration.Config<FrameViewConfig> {
|
||||
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// ///
|
||||
// [JsonConverter (typeof (JsonStringEnumConverter))]
|
||||
// public BorderStyle? DefaultBorderStyle { get; set; }
|
||||
|
||||
// public override void Apply ()
|
||||
// {
|
||||
// if (DefaultBorderStyle.HasValue) {
|
||||
// FrameView.DefaultBorderStyle = DefaultBorderStyle.Value;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public override void CopyUpdatedProperitesFrom (FrameViewConfig changedConfig)
|
||||
// {
|
||||
// if (changedConfig.DefaultBorderStyle.HasValue) {
|
||||
// DefaultBorderStyle = changedConfig.DefaultBorderStyle;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public override void GetHardCodedDefaults ()
|
||||
// {
|
||||
// DefaultBorderStyle = FrameView.DefaultBorderStyle;
|
||||
// }
|
||||
//}
|
||||
|
||||
//[Configuration.ConfigProperty]
|
||||
//internal static FrameViewConfig Config { get; set; } = new FrameViewConfig ();
|
||||
|
||||
View contentView;
|
||||
ustring title;
|
||||
|
||||
@@ -108,13 +144,22 @@ namespace Terminal.Gui {
|
||||
/// </summary>
|
||||
public FrameView () : this (title: string.Empty) { }
|
||||
|
||||
/// <summary>
|
||||
/// The default <see cref="BorderStyle"/> for <see cref="FrameView"/>. The default is <see cref="BorderStyle.Single"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property can be set in a Theme to change the default <see cref="BorderStyle"/> for all <see cref="FrameView"/>s.
|
||||
/// </remarks>
|
||||
[SerializableConfigurationProperty (Scope = typeof (ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
|
||||
public static BorderStyle DefaultBorderStyle { get; set; } = BorderStyle.Single;
|
||||
|
||||
void Initialize (Rect frame, ustring title, View [] views = null, Border border = null)
|
||||
{
|
||||
if (title == null) title = ustring.Empty;
|
||||
this.Title = title;
|
||||
if (border == null) {
|
||||
Border = new Border () {
|
||||
BorderStyle = BorderStyle.Single
|
||||
BorderStyle = DefaultBorderStyle
|
||||
};
|
||||
} else {
|
||||
Border = border;
|
||||
|
||||
@@ -76,8 +76,8 @@ namespace Terminal.Gui {
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="ListView"/> can display any object that implements the <see cref="IList"/> interface.
|
||||
/// <see cref="string"/> values are converted into <see cref="ustring"/> values before rendering, and other values are
|
||||
/// converted into <see cref="string"/> by calling <see cref="object.ToString"/> and then converting to <see cref="ustring"/> .
|
||||
/// <see cref="string"/> values are converted into <see cref="NStack.ustring"/> values before rendering, and other values are
|
||||
/// converted into <see cref="string"/> by calling <see cref="object.ToString"/> and then converting to <see cref="NStack.ustring"/> .
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To change the contents of the ListView, set the <see cref="Source"/> property (when
|
||||
@@ -815,7 +815,10 @@ namespace Terminal.Gui {
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Provides a default implementation of <see cref="IListDataSource"/> that renders
|
||||
/// <see cref="ListView"/> items using <see cref="object.ToString()"/>.
|
||||
/// </summary>
|
||||
public class ListWrapper : IListDataSource {
|
||||
IList src;
|
||||
BitArray marks;
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Gets the global shortcut to invoke the action on the menu.
|
||||
/// </summary>
|
||||
public Key Shortcut { get; }
|
||||
public Key Shortcut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title.
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Terminal.Gui {
|
||||
/// This event is raised when the <see cref="Text"/> changes.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// The passed <see cref="EventArgs"/> is a <see cref="ustring"/> containing the old value.
|
||||
/// The passed <see cref="EventArgs"/> is a <see cref="NStack.ustring"/> containing the old value.
|
||||
/// </remarks>
|
||||
public event Action<ustring> TextChanged;
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using NStack;
|
||||
using Terminal.Gui.Configuration;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
@@ -20,6 +23,26 @@ namespace Terminal.Gui {
|
||||
/// or buttons added to the dialog calls <see cref="Application.RequestStop"/>.
|
||||
/// </remarks>
|
||||
public class Dialog : Window {
|
||||
/// <summary>
|
||||
/// The default <see cref="ButtonAlignments"/> for <see cref="Dialog"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property can be set in a Theme.
|
||||
/// </remarks>
|
||||
[SerializableConfigurationProperty (Scope = typeof (ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
|
||||
public static ButtonAlignments DefaultButtonAlignment { get; set; } = ButtonAlignments.Center;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the default border styling for <see cref="Dialog"/>. Can be configured via <see cref="ConfigurationManager"/>.
|
||||
/// </summary>
|
||||
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static Border DefaultBorder { get; set; } = new Border () {
|
||||
BorderStyle = BorderStyle.Single,
|
||||
DrawMarginFrame = false,
|
||||
Effect3D = true,
|
||||
Effect3DOffset = new Point (1, 1),
|
||||
};
|
||||
|
||||
internal List<Button> buttons = new List<Button> ();
|
||||
const int padding = 0;
|
||||
|
||||
@@ -54,7 +77,8 @@ namespace Terminal.Gui {
|
||||
|
||||
ColorScheme = Colors.Dialog;
|
||||
Modal = true;
|
||||
Border.Effect3D = true;
|
||||
ButtonAlignment = DefaultButtonAlignment;
|
||||
Border = DefaultBorder;
|
||||
|
||||
if (buttons != null) {
|
||||
foreach (var b in buttons) {
|
||||
@@ -117,6 +141,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
return buttons.Select (b => b.Bounds.Width).Sum ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the horizontal alignment of the Dialog buttons.
|
||||
/// </summary>
|
||||
@@ -142,13 +167,12 @@ namespace Terminal.Gui {
|
||||
Right
|
||||
}
|
||||
|
||||
private ButtonAlignments buttonAlignment = Dialog.ButtonAlignments.Center;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how the <see cref="Dialog"/> <see cref="Button"/>s are aligned along the
|
||||
/// bottom of the dialog.
|
||||
/// </summary>
|
||||
public ButtonAlignments ButtonAlignment { get => buttonAlignment; set => buttonAlignment = value; }
|
||||
public ButtonAlignments ButtonAlignment { get; set; }
|
||||
|
||||
void LayoutStartedHandler ()
|
||||
{
|
||||
|
||||
34
Terminal.sln
34
Terminal.sln
@@ -31,23 +31,6 @@ Global
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.Build.0 = Release|Any CPU
|
||||
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.Build.0 = Release|Any CPU
|
||||
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
@@ -56,7 +39,14 @@ Global
|
||||
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.Build.0 = Release|Any CPU
|
||||
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8B901EDE-8974-4820-B100-5226917E2990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B901EDE-8974-4820-B100-5226917E2990}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B901EDE-8974-4820-B100-5226917E2990}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
@@ -73,6 +63,14 @@ Global
|
||||
{44E15B48-0DB2-4560-82BD-D3B7989811C3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{44E15B48-0DB2-4560-82BD-D3B7989811C3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{44E15B48-0DB2-4560-82BD-D3B7989811C3}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
183
UICatalog/Resources/config.json
Normal file
183
UICatalog/Resources/config.json
Normal file
@@ -0,0 +1,183 @@
|
||||
{
|
||||
"$schema": "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json",
|
||||
"Application.QuitKey": {
|
||||
"Key": "Esc"
|
||||
},
|
||||
"AppSettings": {
|
||||
"UICatalog.StatusBar": true,
|
||||
"ConfigurationEditor.EditorColorScheme": {
|
||||
"Normal": {
|
||||
"Foreground": "Red",
|
||||
"Background": "White"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Green",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Black",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "White",
|
||||
"Background": "BrightRed"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "White"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Themes": [
|
||||
{
|
||||
"UI Catalog Theme": {
|
||||
"ColorSchemes": [
|
||||
{
|
||||
"UI Catalog Scheme": {
|
||||
"Normal": {
|
||||
"Foreground": "White",
|
||||
"Background": "Green"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Green",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Blue",
|
||||
"Background": "Green"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "BrightRed",
|
||||
"Background": "White"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "BrightGreen",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TopLevel": {
|
||||
"Normal": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "White"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Black",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "BrightGreen",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Cyan",
|
||||
"Background": "White"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "White"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Base": {
|
||||
"Normal": {
|
||||
"Foreground": "White",
|
||||
"Background": "Green"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Green",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Blue",
|
||||
"Background": "Green"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "BrightRed",
|
||||
"Background": "White"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "BrightGreen",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Dialog": {
|
||||
"Normal": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "Green"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Green",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Blue",
|
||||
"Background": "Green"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Black",
|
||||
"Background": "White"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "BrightGreen",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Menu": {
|
||||
"Normal": {
|
||||
"Foreground": "Black",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Green",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Green",
|
||||
"Background": "Gray"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "DarkGray"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "Gray",
|
||||
"Background": "White"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Error": {
|
||||
"Normal": {
|
||||
"Foreground": "BrightRed",
|
||||
"Background": "BrightYellow"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Black",
|
||||
"Background": "BrightYellow"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "BrightYellow"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "Red",
|
||||
"Background": "BrightYellow"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "BrightGreen",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Terminal.Gui;
|
||||
using Terminal.Gui.Configuration;
|
||||
|
||||
namespace UICatalog {
|
||||
/// <summary>
|
||||
@@ -14,7 +15,7 @@ namespace UICatalog {
|
||||
/// <item><description>Annotate the <see cref="Scenario"/> derived class with a <see cref="Scenario.ScenarioMetadata"/> attribute specifying the scenario's name and description.</description></item>
|
||||
/// <item><description>Add one or more <see cref="Scenario.ScenarioCategory"/> attributes to the class specifying which categories the scenario belongs to. If you don't specify a category the scenario will show up in "_All".</description></item>
|
||||
/// <item><description>Implement the <see cref="Setup"/> override which will be called when a user selects the scenario to run.</description></item>
|
||||
/// <item><description>Optionally, implement the <see cref="Init(Toplevel, ColorScheme)"/> and/or <see cref="Run"/> overrides to provide a custom implementation.</description></item>
|
||||
/// <item><description>Optionally, implement the <see cref="Init(ColorScheme)"/> and/or <see cref="Run"/> overrides to provide a custom implementation.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
@@ -71,9 +72,27 @@ namespace UICatalog {
|
||||
/// </remarks>
|
||||
public virtual void Init (ColorScheme colorScheme)
|
||||
{
|
||||
//ConfigurationManager.Applied += (a) => {
|
||||
// if (Application.Top == null) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// //// Apply changes that apply to either UICatalogTopLevel or a Scenario
|
||||
// //if (Application.Top.MenuBar != null) {
|
||||
// // Application.Top.MenuBar.ColorScheme = Colors.ColorSchemes ["Menu"];
|
||||
// // Application.Top.MenuBar.SetNeedsDisplay ();
|
||||
// //}
|
||||
|
||||
// //if (Application.Top.StatusBar != null) {
|
||||
// // Application.Top.StatusBar.ColorScheme = Colors.ColorSchemes ["Menu"];
|
||||
// // Application.Top.StatusBar.SetNeedsDisplay ();
|
||||
// //}
|
||||
// //Application.Top.SetNeedsDisplay ();
|
||||
//};
|
||||
|
||||
Application.Init ();
|
||||
|
||||
Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
|
||||
Win = new Window ($"{Application.QuitKey} to Close - Scenario: {GetName ()}") {
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (),
|
||||
|
||||
223
UICatalog/Scenarios/ConfigurationEditor.cs
Normal file
223
UICatalog/Scenarios/ConfigurationEditor.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using NStack;
|
||||
using System;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
using Terminal.Gui;
|
||||
using Terminal.Gui.Configuration;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
using Attribute = Terminal.Gui.Attribute;
|
||||
|
||||
namespace UICatalog.Scenarios {
|
||||
[ScenarioMetadata (Name: "Configuration Editor", Description: "Edits Terminal.Gui Config Files.")]
|
||||
[ScenarioCategory ("TabView"), ScenarioCategory ("Colors"), ScenarioCategory ("Files and IO"), ScenarioCategory ("TextView")]
|
||||
public class ConfigurationEditor : Scenario {
|
||||
TileView _tileView;
|
||||
StatusItem _lenStatusItem;
|
||||
|
||||
private static ColorScheme _editorColorScheme = new ColorScheme () {
|
||||
Normal = new Attribute (Color.Red, Color.White),
|
||||
Focus = new Attribute (Color.Red, Color.Black),
|
||||
HotFocus = new Attribute (Color.BrightRed, Color.Black),
|
||||
HotNormal = new Attribute (Color.Magenta, Color.White)
|
||||
};
|
||||
|
||||
[SerializableConfigurationProperty (Scope = typeof (AppScope))]
|
||||
public static ColorScheme EditorColorScheme {
|
||||
get => _editorColorScheme;
|
||||
set {
|
||||
_editorColorScheme = value;
|
||||
_editorColorSchemeChanged?.Invoke ();
|
||||
}
|
||||
}
|
||||
|
||||
private static Action _editorColorSchemeChanged;
|
||||
|
||||
// Don't create a Window, just return the top-level view
|
||||
public override void Init (ColorScheme colorScheme)
|
||||
{
|
||||
Application.Init ();
|
||||
Application.Top.ColorScheme = colorScheme;
|
||||
}
|
||||
|
||||
public override void Setup ()
|
||||
{
|
||||
_tileView = new TileView (0) {
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (1),
|
||||
Orientation = Terminal.Gui.Graphs.Orientation.Vertical,
|
||||
Border = new Border () { BorderStyle = BorderStyle.Single }
|
||||
};
|
||||
|
||||
Application.Top.Add (_tileView);
|
||||
|
||||
_lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null);
|
||||
var statusBar = new StatusBar (new StatusItem [] {
|
||||
new StatusItem(Application.QuitKey, $"{Application.QuitKey} Quit", () => Quit()),
|
||||
new StatusItem(Key.F5, "~F5~ Reload", () => Reload()),
|
||||
new StatusItem(Key.CtrlMask | Key.S, "~^S~ Save", () => Save()),
|
||||
_lenStatusItem,
|
||||
});
|
||||
|
||||
Application.Top.Add (statusBar);
|
||||
|
||||
Open ();
|
||||
|
||||
ConfigurationEditor._editorColorSchemeChanged += () => {
|
||||
foreach (var t in _tileView.Tiles) {
|
||||
t.ContentView.ColorScheme = ConfigurationEditor.EditorColorScheme;
|
||||
t.ContentView.SetNeedsDisplay ();
|
||||
};
|
||||
};
|
||||
|
||||
ConfigurationEditor._editorColorSchemeChanged.Invoke ();
|
||||
|
||||
}
|
||||
|
||||
private class ConfigTextView : TextView {
|
||||
internal TileView.Tile Tile { get; set; }
|
||||
internal FileInfo FileInfo { get; set; }
|
||||
|
||||
internal ConfigTextView ()
|
||||
{
|
||||
ContentsChanged += (obj) => {
|
||||
if (IsDirty) {
|
||||
if (!Tile.Title.EndsWith ('*')) {
|
||||
Tile.Title += '*';
|
||||
} else {
|
||||
Tile.Title = Tile.Title.TrimEnd ('*');
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
internal void Read ()
|
||||
{
|
||||
Assembly assembly = null;
|
||||
if (FileInfo.FullName.Contains ("[Terminal.Gui]")) {
|
||||
// Library resources
|
||||
assembly = typeof (ConfigurationManager).Assembly;
|
||||
} else if (FileInfo.FullName.Contains ("[UICatalog]")) {
|
||||
assembly = Assembly.GetEntryAssembly ();
|
||||
}
|
||||
if (assembly != null) {
|
||||
string name = assembly
|
||||
.GetManifestResourceNames ()
|
||||
.FirstOrDefault (x => x.EndsWith ("config.json"));
|
||||
using Stream stream = assembly.GetManifestResourceStream (name);
|
||||
using StreamReader reader = new StreamReader (stream);
|
||||
Text = reader.ReadToEnd ();
|
||||
ReadOnly = true;
|
||||
Enabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FileInfo.Exists) {
|
||||
// Create empty config file
|
||||
Text = ConfigurationManager.GetEmptyJson ();
|
||||
} else {
|
||||
Text = File.ReadAllText (FileInfo.FullName);
|
||||
}
|
||||
Tile.Title = Tile.Title.TrimEnd ('*');
|
||||
}
|
||||
|
||||
internal void Save ()
|
||||
{
|
||||
if (!Directory.Exists (FileInfo.DirectoryName)) {
|
||||
// Create dir
|
||||
Directory.CreateDirectory (FileInfo.DirectoryName!);
|
||||
}
|
||||
using var writer = File.CreateText (FileInfo.FullName);
|
||||
writer.Write (Text.ToString ());
|
||||
writer.Close ();
|
||||
Tile.Title = Tile.Title.TrimEnd ('*');
|
||||
//IsDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Open ()
|
||||
{
|
||||
var subMenu = new MenuBarItem () {
|
||||
Title = "_View",
|
||||
};
|
||||
|
||||
foreach (var configFile in ConfigurationManager.Settings.Sources) {
|
||||
|
||||
var homeDir = $"{Environment.GetFolderPath (Environment.SpecialFolder.UserProfile)}";
|
||||
FileInfo fileInfo = new FileInfo (configFile.Replace ("~", homeDir));
|
||||
|
||||
var tile = _tileView.InsertTile (_tileView.Tiles.Count);
|
||||
tile.Title = configFile.StartsWith ("resource://") ? fileInfo.Name : configFile;
|
||||
|
||||
var textView = new ConfigTextView () {
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill (),
|
||||
Height = Dim.Fill (),
|
||||
FileInfo = fileInfo,
|
||||
Tile = tile
|
||||
};
|
||||
|
||||
tile.ContentView.Add (textView);
|
||||
|
||||
textView.Read ();
|
||||
|
||||
textView.Enter += (a) => {
|
||||
_lenStatusItem.Title = $"Len:{textView.Text.Length}";
|
||||
};
|
||||
|
||||
//var mi = new MenuItem () {
|
||||
// Title = tile.Title,
|
||||
// CheckType = MenuItemCheckStyle.Checked,
|
||||
// Checked = true,
|
||||
//};
|
||||
//mi.Action += () => {
|
||||
// mi.Checked =! mi.Checked;
|
||||
// _tileView.SetNeedsDisplay ();
|
||||
//};
|
||||
|
||||
//subMenu.Children = subMenu.Children.Append (mi).ToArray ();
|
||||
}
|
||||
|
||||
//var menu = new MenuBar (new MenuBarItem [] { subMenu });
|
||||
//Application.Top.Add (menu);
|
||||
|
||||
}
|
||||
|
||||
private void Reload ()
|
||||
{
|
||||
if (_tileView.MostFocused is ConfigTextView editor) {
|
||||
editor.Read ();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save ()
|
||||
{
|
||||
if (_tileView.MostFocused is ConfigTextView editor) {
|
||||
editor.Save ();
|
||||
}
|
||||
}
|
||||
|
||||
private void Quit ()
|
||||
{
|
||||
foreach (var tile in _tileView.Tiles) {
|
||||
ConfigTextView editor = tile.ContentView.Subviews [0] as ConfigTextView;
|
||||
if (editor.IsDirty) {
|
||||
int result = MessageBox.Query ("Save Changes", $"Save changes to {editor.FileInfo.FullName}", "Yes", "No", "Cancel");
|
||||
if (result == -1 || result == 2) {
|
||||
// user cancelled
|
||||
}
|
||||
if (result == 0) {
|
||||
editor.Save ();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Application.RequestStop ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,12 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Terminal.Gui;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
using Rune = System.Rune;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Terminal.Gui.Configuration;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// UI Catalog is a comprehensive sample library for Terminal.Gui. It provides a simple UI for adding to the catalog of scenarios.
|
||||
@@ -45,6 +49,15 @@ namespace UICatalog {
|
||||
/// UI Catalog is a comprehensive sample app and scenario library for <see cref="Terminal.Gui"/>
|
||||
/// </summary>
|
||||
class UICatalogApp {
|
||||
//[SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true), JsonPropertyName ("UICatalog.StatusBar")]
|
||||
//public static bool ShowStatusBar { get; set; } = true;
|
||||
|
||||
[SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true), JsonPropertyName("UICatalog.StatusBar")]
|
||||
public static bool ShowStatusBar { get; set; } = true;
|
||||
|
||||
static readonly FileSystemWatcher _currentDirWatcher = new FileSystemWatcher ();
|
||||
static readonly FileSystemWatcher _homeDirWatcher = new FileSystemWatcher ();
|
||||
|
||||
static void Main (string [] args)
|
||||
{
|
||||
Console.OutputEncoding = Encoding.Default;
|
||||
@@ -62,6 +75,8 @@ namespace UICatalog {
|
||||
args = args.Where (val => val != "-usc").ToArray ();
|
||||
}
|
||||
|
||||
StartConfigFileWatcher ();
|
||||
|
||||
// If a Scenario name has been provided on the commandline
|
||||
// run it and exit when done.
|
||||
if (args.Length > 0) {
|
||||
@@ -69,7 +84,7 @@ namespace UICatalog {
|
||||
_selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ());
|
||||
Application.UseSystemConsole = _useSystemConsole;
|
||||
Application.Init ();
|
||||
_selectedScenario.Init (_colorScheme);
|
||||
_selectedScenario.Init (Colors.ColorSchemes [_topLevelColorScheme]);
|
||||
_selectedScenario.Setup ();
|
||||
_selectedScenario.Run ();
|
||||
_selectedScenario = null;
|
||||
@@ -92,7 +107,7 @@ namespace UICatalog {
|
||||
Scenario scenario;
|
||||
while ((scenario = RunUICatalogTopLevel ()) != null) {
|
||||
VerifyObjectsWereDisposed ();
|
||||
scenario.Init (_colorScheme);
|
||||
scenario.Init (Colors.ColorSchemes [_topLevelColorScheme]);
|
||||
scenario.Setup ();
|
||||
scenario.Run ();
|
||||
|
||||
@@ -102,9 +117,66 @@ namespace UICatalog {
|
||||
|
||||
VerifyObjectsWereDisposed ();
|
||||
}
|
||||
|
||||
StopConfigFileWatcher ();
|
||||
VerifyObjectsWereDisposed ();
|
||||
}
|
||||
|
||||
private static void StopConfigFileWatcher() {
|
||||
_currentDirWatcher.EnableRaisingEvents = false;
|
||||
_currentDirWatcher.Changed -= ConfigFileChanged;
|
||||
_currentDirWatcher.Created -= ConfigFileChanged;
|
||||
|
||||
_homeDirWatcher.EnableRaisingEvents = false;
|
||||
_homeDirWatcher.Changed -= ConfigFileChanged;
|
||||
_homeDirWatcher.Created -= ConfigFileChanged;
|
||||
}
|
||||
|
||||
private static void StartConfigFileWatcher()
|
||||
{
|
||||
// Setup a file system watcher for `./.tui/`
|
||||
_currentDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
||||
var f = new FileInfo (Assembly.GetExecutingAssembly ().Location);
|
||||
var tuiDir = Path.Combine (f.Directory.FullName, ".tui");
|
||||
|
||||
if (!Directory.Exists (tuiDir)) {
|
||||
Directory.CreateDirectory (tuiDir);
|
||||
}
|
||||
_currentDirWatcher.Path = tuiDir;
|
||||
_currentDirWatcher.Filter = "*config.json";
|
||||
|
||||
// Setup a file system watcher for `~/.tui/`
|
||||
_homeDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
||||
f = new FileInfo (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
|
||||
tuiDir = Path.Combine (f.FullName, ".tui");
|
||||
|
||||
if (!Directory.Exists (tuiDir)) {
|
||||
Directory.CreateDirectory (tuiDir);
|
||||
}
|
||||
_homeDirWatcher.Path = tuiDir;
|
||||
_homeDirWatcher.Filter = "*config.json";
|
||||
|
||||
_currentDirWatcher.Changed += ConfigFileChanged;
|
||||
//_currentDirWatcher.Created += ConfigFileChanged;
|
||||
_currentDirWatcher.EnableRaisingEvents = true;
|
||||
|
||||
_homeDirWatcher.Changed += ConfigFileChanged;
|
||||
//_homeDirWatcher.Created += ConfigFileChanged;
|
||||
_homeDirWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
private static void ConfigFileChanged (object sender, FileSystemEventArgs e)
|
||||
{
|
||||
if (Application.Top == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TOOD: THis is a hack. Figure out how to ensure that the file is fully written before reading it.
|
||||
Thread.Sleep (500);
|
||||
ConfigurationManager.Load ();
|
||||
ConfigurationManager.Apply ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the UI Catalog selection UI. When the user selects a Scenario to run, the
|
||||
/// UI Catalog main app UI is killed and the Scenario is run as though it were Application.Top.
|
||||
@@ -139,15 +211,17 @@ namespace UICatalog {
|
||||
|
||||
static bool _useSystemConsole = false;
|
||||
static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
|
||||
static bool _heightAsBuffer = false;
|
||||
static bool _isFirstRunning = true;
|
||||
static ColorScheme _colorScheme;
|
||||
static string _topLevelColorScheme;
|
||||
|
||||
static MenuItem [] _themeMenuItems;
|
||||
static MenuBarItem _themeMenuBarItem;
|
||||
|
||||
/// <summary>
|
||||
/// This is the main UI Catalog app view. It is run fresh when the app loads (if a Scenario has not been passed on
|
||||
/// the command line) and each time a Scenario ends.
|
||||
/// </summary>
|
||||
class UICatalogTopLevel : Toplevel {
|
||||
public class UICatalogTopLevel : Toplevel {
|
||||
public MenuItem miIsMouseDisabled;
|
||||
public MenuItem miHeightAsBuffer;
|
||||
|
||||
@@ -163,12 +237,13 @@ namespace UICatalog {
|
||||
|
||||
public UICatalogTopLevel ()
|
||||
{
|
||||
ColorScheme = _colorScheme = Colors.Base;
|
||||
_themeMenuItems = CreateThemeMenuItems ();
|
||||
_themeMenuBarItem = new MenuBarItem ("_Themes", _themeMenuItems);
|
||||
MenuBar = new MenuBar (new MenuBarItem [] {
|
||||
new MenuBarItem ("_File", new MenuItem [] {
|
||||
new MenuItem ("_Quit", "Quit UI Catalog", () => RequestStop(), null, null, Key.Q | Key.CtrlMask)
|
||||
new MenuItem ("_Quit", "Quit UI Catalog", () => RequestStop(), null, null)
|
||||
}),
|
||||
new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()),
|
||||
_themeMenuBarItem,
|
||||
new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()),
|
||||
new MenuBarItem ("_Help", new MenuItem [] {
|
||||
new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1),
|
||||
@@ -185,10 +260,11 @@ namespace UICatalog {
|
||||
OS = new StatusItem (Key.CharMask, "OS:", null);
|
||||
|
||||
StatusBar = new StatusBar () {
|
||||
Visible = true,
|
||||
Visible = UICatalogApp.ShowStatusBar
|
||||
};
|
||||
|
||||
StatusBar.Items = new StatusItem [] {
|
||||
new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => {
|
||||
new StatusItem(Application.QuitKey, $"~{Application.QuitKey} to quit", () => {
|
||||
if (_selectedScenario is null){
|
||||
// This causes GetScenarioToRun to return null
|
||||
_selectedScenario = null;
|
||||
@@ -232,7 +308,7 @@ namespace UICatalog {
|
||||
};
|
||||
CategoryListView.SelectedItemChanged += CategoryListView_SelectedChanged;
|
||||
|
||||
ContentPane.Tiles.ElementAt(0).Title = "Categories";
|
||||
ContentPane.Tiles.ElementAt (0).Title = "Categories";
|
||||
ContentPane.Tiles.ElementAt (0).MinSize = 2;
|
||||
ContentPane.Tiles.ElementAt (0).ContentView.Add (CategoryListView);
|
||||
|
||||
@@ -258,22 +334,19 @@ namespace UICatalog {
|
||||
Add (StatusBar);
|
||||
|
||||
Loaded += LoadedHandler;
|
||||
Unloaded += UnloadedHandler;
|
||||
|
||||
// Restore previous selections
|
||||
CategoryListView.SelectedItem = _cachedCategoryIndex;
|
||||
ScenarioListView.SelectedItem = _cachedScenarioIndex;
|
||||
|
||||
ConfigurationManager.Applied += ConfigAppliedHandler;
|
||||
}
|
||||
|
||||
void LoadedHandler ()
|
||||
{
|
||||
Application.HeightAsBuffer = _heightAsBuffer;
|
||||
ConfigChanged ();
|
||||
|
||||
if (_colorScheme == null) {
|
||||
ColorScheme = _colorScheme = Colors.Base;
|
||||
}
|
||||
|
||||
miIsMouseDisabled.Checked = Application.IsMouseDisabled;
|
||||
miHeightAsBuffer.Checked = Application.HeightAsBuffer;
|
||||
DriverName.Title = $"Driver: {Driver.GetType ().Name}";
|
||||
OS.Title = $"OS: {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem} {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion}";
|
||||
|
||||
@@ -284,9 +357,30 @@ namespace UICatalog {
|
||||
if (!_isFirstRunning) {
|
||||
ScenarioListView.SetFocus ();
|
||||
}
|
||||
|
||||
StatusBar.VisibleChanged += () => {
|
||||
UICatalogApp.ShowStatusBar = StatusBar.Visible;
|
||||
|
||||
var height = (StatusBar.Visible ? 1 : 0);// + (MenuBar.Visible ? 1 : 0);
|
||||
ContentPane.Height = Dim.Fill (height);
|
||||
LayoutSubviews ();
|
||||
SetChildNeedsDisplay ();
|
||||
};
|
||||
|
||||
Loaded -= LoadedHandler;
|
||||
}
|
||||
|
||||
private void UnloadedHandler ()
|
||||
{
|
||||
ConfigurationManager.Applied -= ConfigAppliedHandler;
|
||||
Unloaded -= UnloadedHandler;
|
||||
}
|
||||
|
||||
void ConfigAppliedHandler (ConfigurationManagerEventArgs a)
|
||||
{
|
||||
ConfigChanged ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launches the selected scenario, setting the global _selectedScenario
|
||||
/// </summary>
|
||||
@@ -307,20 +401,22 @@ namespace UICatalog {
|
||||
|
||||
List<MenuItem []> CreateDiagnosticMenuItems ()
|
||||
{
|
||||
List<MenuItem []> menuItems = new List<MenuItem []> ();
|
||||
menuItems.Add (CreateDiagnosticFlagsMenuItems ());
|
||||
menuItems.Add (new MenuItem [] { null });
|
||||
menuItems.Add (CreateHeightAsBufferMenuItems ());
|
||||
menuItems.Add (CreateDisabledEnabledMouseItems ());
|
||||
menuItems.Add (CreateKeybindingsMenuItems ());
|
||||
List<MenuItem []> menuItems = new List<MenuItem []> {
|
||||
CreateDiagnosticFlagsMenuItems (),
|
||||
new MenuItem [] { null },
|
||||
CreateHeightAsBufferMenuItems (),
|
||||
CreateDisabledEnabledMouseItems (),
|
||||
CreateKeybindingsMenuItems ()
|
||||
};
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
MenuItem [] CreateDisabledEnabledMouseItems ()
|
||||
{
|
||||
List<MenuItem> menuItems = new List<MenuItem> ();
|
||||
miIsMouseDisabled = new MenuItem ();
|
||||
miIsMouseDisabled.Title = "_Disable Mouse";
|
||||
miIsMouseDisabled = new MenuItem {
|
||||
Title = "_Disable Mouse"
|
||||
};
|
||||
miIsMouseDisabled.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miIsMouseDisabled.Title.ToString ().Substring (1, 1) [0];
|
||||
miIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked;
|
||||
miIsMouseDisabled.Action += () => {
|
||||
@@ -334,9 +430,10 @@ namespace UICatalog {
|
||||
MenuItem [] CreateKeybindingsMenuItems ()
|
||||
{
|
||||
List<MenuItem> menuItems = new List<MenuItem> ();
|
||||
var item = new MenuItem ();
|
||||
item.Title = "_Key Bindings";
|
||||
item.Help = "Change which keys do what";
|
||||
var item = new MenuItem {
|
||||
Title = "_Key Bindings",
|
||||
Help = "Change which keys do what"
|
||||
};
|
||||
item.Action += () => {
|
||||
var dlg = new KeyBindingsDialog ();
|
||||
Application.Run (dlg);
|
||||
@@ -351,8 +448,9 @@ namespace UICatalog {
|
||||
MenuItem [] CreateHeightAsBufferMenuItems ()
|
||||
{
|
||||
List<MenuItem> menuItems = new List<MenuItem> ();
|
||||
miHeightAsBuffer = new MenuItem ();
|
||||
miHeightAsBuffer.Title = "_Height As Buffer";
|
||||
miHeightAsBuffer = new MenuItem {
|
||||
Title = "_Height As Buffer"
|
||||
};
|
||||
miHeightAsBuffer.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miHeightAsBuffer.Title.ToString ().Substring (1, 1) [0];
|
||||
miHeightAsBuffer.CheckType |= MenuItemCheckStyle.Checked;
|
||||
miHeightAsBuffer.Action += () => {
|
||||
@@ -373,9 +471,10 @@ namespace UICatalog {
|
||||
|
||||
List<MenuItem> menuItems = new List<MenuItem> ();
|
||||
foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) {
|
||||
var item = new MenuItem ();
|
||||
item.Title = GetDiagnosticsTitle (diag);
|
||||
item.Shortcut = Key.AltMask + index.ToString () [0];
|
||||
var item = new MenuItem {
|
||||
Title = GetDiagnosticsTitle (diag),
|
||||
Shortcut = Key.AltMask + index.ToString () [0]
|
||||
};
|
||||
index++;
|
||||
item.CheckType |= MenuItemCheckStyle.Checked;
|
||||
if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title) {
|
||||
@@ -417,26 +516,21 @@ namespace UICatalog {
|
||||
|
||||
string GetDiagnosticsTitle (Enum diag)
|
||||
{
|
||||
switch (Enum.GetName (_diagnosticFlags.GetType (), diag)) {
|
||||
case "Off":
|
||||
return OFF;
|
||||
case "FrameRuler":
|
||||
return FRAME_RULER;
|
||||
case "FramePadding":
|
||||
return FRAME_PADDING;
|
||||
}
|
||||
return "";
|
||||
return Enum.GetName (_diagnosticFlags.GetType (), diag) switch {
|
||||
"Off" => OFF,
|
||||
"FrameRuler" => FRAME_RULER,
|
||||
"FramePadding" => FRAME_PADDING,
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
|
||||
Enum GetDiagnosticsEnumValue (ustring title)
|
||||
{
|
||||
switch (title.ToString ()) {
|
||||
case FRAME_RULER:
|
||||
return ConsoleDriver.DiagnosticFlags.FrameRuler;
|
||||
case FRAME_PADDING:
|
||||
return ConsoleDriver.DiagnosticFlags.FramePadding;
|
||||
}
|
||||
return null;
|
||||
return title.ToString () switch {
|
||||
FRAME_RULER => ConsoleDriver.DiagnosticFlags.FrameRuler,
|
||||
FRAME_PADDING => ConsoleDriver.DiagnosticFlags.FramePadding,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
void SetDiagnosticsFlag (Enum diag, bool add)
|
||||
@@ -463,27 +557,91 @@ namespace UICatalog {
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem [] CreateColorSchemeMenuItems ()
|
||||
public MenuItem [] CreateThemeMenuItems ()
|
||||
{
|
||||
List<MenuItem> menuItems = new List<MenuItem> ();
|
||||
foreach (var sc in Colors.ColorSchemes) {
|
||||
var item = new MenuItem ();
|
||||
item.Title = $"_{sc.Key}";
|
||||
item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0];
|
||||
item.CheckType |= MenuItemCheckStyle.Radio;
|
||||
item.Checked = sc.Value == _colorScheme;
|
||||
foreach (var theme in ConfigurationManager.Themes) {
|
||||
var item = new MenuItem {
|
||||
Title = theme.Key,
|
||||
Shortcut = Key.AltMask + theme.Key [0]
|
||||
};
|
||||
item.CheckType |= MenuItemCheckStyle.Checked;
|
||||
item.Checked = theme.Key == ConfigurationManager.Themes.Theme;
|
||||
item.Action += () => {
|
||||
ColorScheme = _colorScheme = sc.Value;
|
||||
SetNeedsDisplay ();
|
||||
foreach (var menuItem in menuItems) {
|
||||
menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _colorScheme;
|
||||
}
|
||||
ConfigurationManager.Themes.Theme = theme.Key;
|
||||
ConfigurationManager.Apply ();
|
||||
};
|
||||
menuItems.Add (item);
|
||||
}
|
||||
|
||||
var schemeMenuItems = new List<MenuItem> ();
|
||||
foreach (var sc in Colors.ColorSchemes) {
|
||||
var item = new MenuItem {
|
||||
Title = $"_{sc.Key}",
|
||||
Data = sc.Key,
|
||||
Shortcut = Key.AltMask | (Key)sc.Key [..1] [0]
|
||||
};
|
||||
item.CheckType |= MenuItemCheckStyle.Radio;
|
||||
item.Checked = sc.Key == _topLevelColorScheme;
|
||||
item.Action += () => {
|
||||
_topLevelColorScheme = (string)item.Data;
|
||||
foreach (var schemeMenuItem in schemeMenuItems) {
|
||||
schemeMenuItem.Checked = (string)schemeMenuItem.Data == _topLevelColorScheme;
|
||||
}
|
||||
ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
|
||||
Application.Top.SetNeedsDisplay ();
|
||||
};
|
||||
schemeMenuItems.Add (item);
|
||||
}
|
||||
menuItems.Add (null);
|
||||
var mbi = new MenuBarItem ("_Color Scheme for Application.Top", schemeMenuItems.ToArray ());
|
||||
menuItems.Add (mbi);
|
||||
|
||||
return menuItems.ToArray ();
|
||||
}
|
||||
|
||||
public void ConfigChanged ()
|
||||
{
|
||||
if (_topLevelColorScheme == null || !Colors.ColorSchemes.ContainsKey (_topLevelColorScheme)) {
|
||||
_topLevelColorScheme = "Base";
|
||||
}
|
||||
|
||||
_themeMenuItems = ((UICatalogTopLevel)Application.Top).CreateThemeMenuItems ();
|
||||
_themeMenuBarItem.Children = _themeMenuItems;
|
||||
|
||||
var checkedThemeMenu = _themeMenuItems.Where (m => (bool)m.Checked).FirstOrDefault ();
|
||||
if (checkedThemeMenu != null) {
|
||||
checkedThemeMenu.Checked = false;
|
||||
}
|
||||
checkedThemeMenu = _themeMenuItems.Where (m => m != null && m.Title == ConfigurationManager.Themes.Theme).FirstOrDefault ();
|
||||
if (checkedThemeMenu != null) {
|
||||
ConfigurationManager.Themes.Theme = checkedThemeMenu.Title.ToString ();
|
||||
checkedThemeMenu.Checked = true;
|
||||
}
|
||||
var schemeMenuItems = ((MenuBarItem)_themeMenuItems.Where (i => i is MenuBarItem).FirstOrDefault ()).Children;
|
||||
foreach (var schemeMenuItem in schemeMenuItems) {
|
||||
schemeMenuItem.Checked = (string)schemeMenuItem.Data == _topLevelColorScheme;
|
||||
}
|
||||
|
||||
ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
|
||||
|
||||
ContentPane.Border.BorderStyle = FrameView.DefaultBorderStyle;
|
||||
|
||||
MenuBar.Menus [0].Children [0].Shortcut = Application.QuitKey;
|
||||
StatusBar.Items [0].Shortcut = Application.QuitKey;
|
||||
StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit";
|
||||
|
||||
miIsMouseDisabled.Checked = Application.IsMouseDisabled;
|
||||
miHeightAsBuffer.Checked = Application.HeightAsBuffer;
|
||||
|
||||
var height = (UICatalogApp.ShowStatusBar ? 1 : 0);// + (MenuBar.Visible ? 1 : 0);
|
||||
ContentPane.Height = Dim.Fill (height);
|
||||
|
||||
StatusBar.Visible = UICatalogApp.ShowStatusBar;
|
||||
|
||||
Application.Top.SetNeedsDisplay ();
|
||||
}
|
||||
|
||||
void KeyDownHandler (View.KeyEventEventArgs a)
|
||||
{
|
||||
if (a.KeyEvent.IsCapslock) {
|
||||
@@ -533,6 +691,7 @@ namespace UICatalog {
|
||||
// after a scenario was selected to run. This proves the main UI Catalog
|
||||
// 'app' closed cleanly.
|
||||
foreach (var inst in Responder.Instances) {
|
||||
|
||||
Debug.Assert (inst.WasDisposed);
|
||||
}
|
||||
Responder.Instances.Clear ();
|
||||
@@ -554,7 +713,7 @@ namespace UICatalog {
|
||||
url = url.Replace ("&", "^&");
|
||||
Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true });
|
||||
} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
|
||||
using (var process = new Process {
|
||||
using var process = new Process {
|
||||
StartInfo = new ProcessStartInfo {
|
||||
FileName = "xdg-open",
|
||||
Arguments = url,
|
||||
@@ -563,9 +722,8 @@ namespace UICatalog {
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false
|
||||
}
|
||||
}) {
|
||||
process.Start ();
|
||||
}
|
||||
};
|
||||
process.Start ();
|
||||
} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
|
||||
Process.Start ("open", url);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\config.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\config.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="./Scenarios/Spinning_globe_dark_small.gif" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace Terminal.Gui.ApplicationTests {
|
||||
Assert.Null (Application.Driver);
|
||||
Assert.Null (Application.Top);
|
||||
Assert.Null (Application.Current);
|
||||
// removed below as HeightAsBuffer now works without a driver loaded
|
||||
//Assert.Throws<ArgumentNullException> (() => Application.HeightAsBuffer == true);
|
||||
Assert.Null (Application.MainLoop);
|
||||
Assert.Null (Application.Iteration);
|
||||
Assert.Null (Application.RootMouseEvent);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Terminal.Gui;
|
||||
using Xunit;
|
||||
|
||||
namespace Terminal.Gui.ApplicationTests {
|
||||
@@ -11,11 +12,8 @@ namespace Terminal.Gui.ApplicationTests {
|
||||
|
||||
int index = toplevels.Count - 1;
|
||||
foreach (var top in toplevels) {
|
||||
if (top.GetType () == typeof (Toplevel)) {
|
||||
Assert.Equal ("Top", top.Id);
|
||||
} else {
|
||||
Assert.Equal ($"w{index}", top.Id);
|
||||
}
|
||||
if (top.GetType () == typeof (Toplevel)) Assert.Equal ("Top", top.Id);
|
||||
else Assert.Equal ($"w{index}", top.Id);
|
||||
index--;
|
||||
}
|
||||
|
||||
@@ -35,7 +33,7 @@ namespace Terminal.Gui.ApplicationTests {
|
||||
|
||||
var valueToReplace = new Window () { Id = "w1" };
|
||||
var valueToReplaceWith = new Window () { Id = "new" };
|
||||
ToplevelEqualityComparer comparer = new ToplevelEqualityComparer ();
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
toplevels.Replace (valueToReplace, valueToReplaceWith, comparer);
|
||||
|
||||
@@ -55,7 +53,7 @@ namespace Terminal.Gui.ApplicationTests {
|
||||
|
||||
var valueToSwapFrom = new Window () { Id = "w3" };
|
||||
var valueToSwapTo = new Window () { Id = "w1" };
|
||||
ToplevelEqualityComparer comparer = new ToplevelEqualityComparer ();
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
toplevels.Swap (valueToSwapFrom, valueToSwapTo, comparer);
|
||||
|
||||
var tops = toplevels.ToArray ();
|
||||
@@ -105,18 +103,16 @@ namespace Terminal.Gui.ApplicationTests {
|
||||
Stack<Toplevel> toplevels = CreateToplevels ();
|
||||
|
||||
// Only allows unique keys
|
||||
HashSet<int> hCodes = new HashSet<int> ();
|
||||
var hCodes = new HashSet<int> ();
|
||||
|
||||
foreach (var top in toplevels) {
|
||||
Assert.True (hCodes.Add (top.GetHashCode ()));
|
||||
}
|
||||
foreach (var top in toplevels) Assert.True (hCodes.Add (top.GetHashCode ()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stack_Toplevels_FindDuplicates ()
|
||||
{
|
||||
Stack<Toplevel> toplevels = CreateToplevels ();
|
||||
ToplevelEqualityComparer comparer = new ToplevelEqualityComparer ();
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
toplevels.Push (new Toplevel () { Id = "w4" });
|
||||
toplevels.Push (new Toplevel () { Id = "w1" });
|
||||
@@ -131,7 +127,7 @@ namespace Terminal.Gui.ApplicationTests {
|
||||
public void Stack_Toplevels_Contains ()
|
||||
{
|
||||
Stack<Toplevel> toplevels = CreateToplevels ();
|
||||
ToplevelEqualityComparer comparer = new ToplevelEqualityComparer ();
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
Assert.True (toplevels.Contains (new Window () { Id = "w2" }, comparer));
|
||||
Assert.False (toplevels.Contains (new Toplevel () { Id = "top2" }, comparer));
|
||||
@@ -143,7 +139,7 @@ namespace Terminal.Gui.ApplicationTests {
|
||||
Stack<Toplevel> toplevels = CreateToplevels ();
|
||||
|
||||
var valueToMove = new Window () { Id = "w1" };
|
||||
ToplevelEqualityComparer comparer = new ToplevelEqualityComparer ();
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
toplevels.MoveTo (valueToMove, 1, comparer);
|
||||
|
||||
@@ -162,7 +158,7 @@ namespace Terminal.Gui.ApplicationTests {
|
||||
Stack<Toplevel> toplevels = CreateToplevels ();
|
||||
|
||||
var valueToMove = new Window () { Id = "Top" };
|
||||
ToplevelEqualityComparer comparer = new ToplevelEqualityComparer ();
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
toplevels.MoveTo (valueToMove, 0, comparer);
|
||||
|
||||
@@ -178,7 +174,7 @@ namespace Terminal.Gui.ApplicationTests {
|
||||
|
||||
private Stack<Toplevel> CreateToplevels ()
|
||||
{
|
||||
Stack<Toplevel> toplevels = new Stack<Toplevel> ();
|
||||
var toplevels = new Stack<Toplevel> ();
|
||||
|
||||
toplevels.Push (new Toplevel () { Id = "Top" });
|
||||
toplevels.Push (new Window () { Id = "w1" });
|
||||
|
||||
@@ -7,7 +7,7 @@ using Xunit;
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
using Console = Terminal.Gui.FakeConsole;
|
||||
|
||||
namespace Terminal.Gui.DriverTests {
|
||||
namespace Terminal.Gui.ColorTests {
|
||||
public class AttributeTests {
|
||||
[Fact]
|
||||
public void Constuctors_Constuct ()
|
||||
89
UnitTests/Configuration/AppScopeTests.cs
Normal file
89
UnitTests/Configuration/AppScopeTests.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using Xunit;
|
||||
using Terminal.Gui.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui.ConfigurationTests {
|
||||
public class AppScopeTests {
|
||||
public static readonly JsonSerializerOptions _jsonOptions = new () {
|
||||
Converters = {
|
||||
//new AttributeJsonConverter (),
|
||||
//new ColorJsonConverter ()
|
||||
}
|
||||
};
|
||||
|
||||
public class AppSettingsTestClass {
|
||||
[SerializableConfigurationProperty (Scope = typeof (AppScope))]
|
||||
public static bool? TestProperty { get; set; } = null;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestNullable ()
|
||||
{
|
||||
AppSettingsTestClass.TestProperty = null;
|
||||
Assert.Null (AppSettingsTestClass.TestProperty);
|
||||
|
||||
ConfigurationManager.Initialize ();
|
||||
ConfigurationManager.GetHardCodedDefaults ();
|
||||
ConfigurationManager.Apply ();
|
||||
Assert.Null (AppSettingsTestClass.TestProperty);
|
||||
|
||||
AppSettingsTestClass.TestProperty = true;
|
||||
ConfigurationManager.Initialize ();
|
||||
ConfigurationManager.GetHardCodedDefaults ();
|
||||
Assert.NotNull (AppSettingsTestClass.TestProperty);
|
||||
ConfigurationManager.Apply ();
|
||||
Assert.NotNull (AppSettingsTestClass.TestProperty);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void Apply_ShouldApplyUpdatedProperties ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
Assert.Null (AppSettingsTestClass.TestProperty);
|
||||
Assert.NotEmpty (ConfigurationManager.AppSettings);
|
||||
Assert.Null (ConfigurationManager.AppSettings ["AppSettingsTestClass.TestProperty"].PropertyValue);
|
||||
|
||||
AppSettingsTestClass.TestProperty = true;
|
||||
ConfigurationManager.Reset ();
|
||||
Assert.True (AppSettingsTestClass.TestProperty);
|
||||
Assert.NotEmpty (ConfigurationManager.AppSettings);
|
||||
Assert.Null (ConfigurationManager.AppSettings ["AppSettingsTestClass.TestProperty"].PropertyValue as bool?);
|
||||
|
||||
ConfigurationManager.AppSettings ["AppSettingsTestClass.TestProperty"].PropertyValue = false;
|
||||
Assert.False (ConfigurationManager.AppSettings ["AppSettingsTestClass.TestProperty"].PropertyValue as bool?);
|
||||
|
||||
// ConfigurationManager.Settings should NOT apply theme settings
|
||||
ConfigurationManager.Settings.Apply ();
|
||||
Assert.True (AppSettingsTestClass.TestProperty);
|
||||
|
||||
// ConfigurationManager.Themes should NOT apply theme settings
|
||||
ConfigurationManager.ThemeManager.Themes! [ThemeManager.SelectedTheme]!.Apply ();
|
||||
Assert.True (AppSettingsTestClass.TestProperty);
|
||||
|
||||
// ConfigurationManager.AppSettings should NOT apply theme settings
|
||||
ConfigurationManager.AppSettings.Apply ();
|
||||
Assert.False (AppSettingsTestClass.TestProperty);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSerialize_RoundTrip ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
var initial = ConfigurationManager.AppSettings;
|
||||
|
||||
var serialized = JsonSerializer.Serialize<AppScope> (ConfigurationManager.AppSettings, _jsonOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<AppScope> (serialized, _jsonOptions);
|
||||
|
||||
Assert.NotEqual (initial, deserialized);
|
||||
Assert.Equal (deserialized.Count, initial.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
833
UnitTests/Configuration/ConfigurationMangerTests.cs
Normal file
833
UnitTests/Configuration/ConfigurationMangerTests.cs
Normal file
@@ -0,0 +1,833 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text.Json;
|
||||
using Terminal.Gui.Configuration;
|
||||
using Xunit;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui.ConfigurationTests {
|
||||
public class ConfigurationManagerTests {
|
||||
|
||||
public static readonly JsonSerializerOptions _jsonOptions = new() {
|
||||
Converters = {
|
||||
new AttributeJsonConverter (),
|
||||
new ColorJsonConverter (),
|
||||
}
|
||||
};
|
||||
|
||||
[Fact ()]
|
||||
public void DeepMemberwiseCopyTest ()
|
||||
{
|
||||
// Value types
|
||||
var stringDest = "Destination";
|
||||
var stringSrc = "Source";
|
||||
var stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
|
||||
Assert.Equal (stringSrc, stringCopy);
|
||||
|
||||
stringDest = "Destination";
|
||||
stringSrc = "Destination";
|
||||
stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
|
||||
Assert.Equal (stringSrc, stringCopy);
|
||||
|
||||
stringDest = "Destination";
|
||||
stringSrc = null;
|
||||
stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
|
||||
Assert.Equal (stringSrc, stringCopy);
|
||||
|
||||
stringDest = "Destination";
|
||||
stringSrc = string.Empty;
|
||||
stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
|
||||
Assert.Equal (stringSrc, stringCopy);
|
||||
|
||||
var boolDest = true;
|
||||
var boolSrc = false;
|
||||
var boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
|
||||
Assert.Equal (boolSrc, boolCopy);
|
||||
|
||||
boolDest = false;
|
||||
boolSrc = true;
|
||||
boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
|
||||
Assert.Equal (boolSrc, boolCopy);
|
||||
|
||||
boolDest = true;
|
||||
boolSrc = true;
|
||||
boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
|
||||
Assert.Equal (boolSrc, boolCopy);
|
||||
|
||||
boolDest = false;
|
||||
boolSrc = false;
|
||||
boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
|
||||
Assert.Equal (boolSrc, boolCopy);
|
||||
|
||||
// Structs
|
||||
var attrDest = new Attribute (1);
|
||||
var attrSrc = new Attribute (2);
|
||||
var attrCopy = DeepMemberwiseCopy (attrSrc, attrDest);
|
||||
Assert.Equal (attrSrc, attrCopy);
|
||||
|
||||
// Classes
|
||||
var colorschemeDest = new ColorScheme () { Disabled = new Attribute (1) };
|
||||
var colorschemeSrc = new ColorScheme () { Disabled = new Attribute (2) };
|
||||
var colorschemeCopy = DeepMemberwiseCopy (colorschemeSrc, colorschemeDest);
|
||||
Assert.Equal (colorschemeSrc, colorschemeCopy);
|
||||
|
||||
// Dictionaries
|
||||
var dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (1) } };
|
||||
var dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (2) } };
|
||||
var dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
|
||||
Assert.Equal (dictSrc, dictCopy);
|
||||
|
||||
dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (1) } };
|
||||
dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (2) }, { "Normal", new Attribute (3) } };
|
||||
dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
|
||||
Assert.Equal (dictSrc, dictCopy);
|
||||
|
||||
// src adds an item
|
||||
dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (1) } };
|
||||
dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (2) }, { "Normal", new Attribute (3) } };
|
||||
dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
|
||||
Assert.Equal (2, dictCopy.Count);
|
||||
Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
|
||||
Assert.Equal (dictSrc ["Normal"], dictCopy ["Normal"]);
|
||||
|
||||
// src updates only one item
|
||||
dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (1) }, { "Normal", new Attribute (2) } };
|
||||
dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (3) } };
|
||||
dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
|
||||
Assert.Equal (2, dictCopy.Count);
|
||||
Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
|
||||
Assert.Equal (dictDest ["Normal"], dictCopy ["Normal"]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
//[Fact ()]
|
||||
//public void LoadFromJsonTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void ToJsonTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void UpdateConfigurationTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void UpdateConfigurationFromFileTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void SaveHardCodedDefaultsTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void LoadGlobalFromLibraryResourceTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void LoadGlobalFromAppDirectoryTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void LoadGlobalFromHomeDirectoryTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void LoadAppFromAppResourcesTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void LoadAppFromAppDirectoryTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void LoadAppFromHomeDirectoryTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
//[Fact ()]
|
||||
//public void LoadTest ()
|
||||
//{
|
||||
// Assert.True (false, "This test needs an implementation");
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Save the `config.json` file; this can be used to update the file in `Terminal.Gui.Resources.config.json'.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// IMPORTANT: For the file generated to be valid, this must be the ONLY test run. Conifg Properties
|
||||
/// are all satic and thus can be overwritten by other tests.</remarks>
|
||||
[Fact]
|
||||
public void SaveDefaults ()
|
||||
{
|
||||
ConfigurationManager.Initialize ();
|
||||
|
||||
// Get the hard coded settings
|
||||
ConfigurationManager.GetHardCodedDefaults ();
|
||||
|
||||
// Serialize to a JSON string
|
||||
string json = ConfigurationManager.ToJson ();
|
||||
|
||||
// Write the JSON string to the file
|
||||
File.WriteAllText ("config.json", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseWithoutResetAsserts ()
|
||||
{
|
||||
ConfigurationManager.Initialize ();
|
||||
Assert.Throws<InvalidOperationException> (() => _ = ConfigurationManager.Settings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_Resets()
|
||||
{
|
||||
ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
|
||||
ConfigurationManager.Reset ();
|
||||
Assert.NotEmpty (ConfigurationManager.Themes);
|
||||
Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_and_ResetLoadWithLibraryResourcesOnly_are_same ()
|
||||
{
|
||||
ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
|
||||
// arrange
|
||||
ConfigurationManager.Reset ();
|
||||
ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
|
||||
ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
|
||||
ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
|
||||
ConfigurationManager.Settings ["Application.UseSystemConsole"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.HeightAsBuffer"].PropertyValue = true;
|
||||
ConfigurationManager.Settings.Apply ();
|
||||
|
||||
// assert apply worked
|
||||
Assert.Equal (Key.Q, Application.QuitKey);
|
||||
Assert.Equal (Key.F, Application.AlternateForwardKey);
|
||||
Assert.Equal (Key.B, Application.AlternateBackwardKey);
|
||||
Assert.True (Application.UseSystemConsole);
|
||||
Assert.True (Application.IsMouseDisabled);
|
||||
Assert.True (Application.HeightAsBuffer);
|
||||
|
||||
//act
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
// assert
|
||||
Assert.NotEmpty (ConfigurationManager.Themes);
|
||||
Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
|
||||
Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey);
|
||||
Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey);
|
||||
Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey);
|
||||
Assert.False (Application.UseSystemConsole);
|
||||
Assert.False (Application.IsMouseDisabled);
|
||||
Assert.False (Application.HeightAsBuffer);
|
||||
|
||||
// arrange
|
||||
ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
|
||||
ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
|
||||
ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
|
||||
ConfigurationManager.Settings ["Application.UseSystemConsole"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.HeightAsBuffer"].PropertyValue = true;
|
||||
ConfigurationManager.Settings.Apply ();
|
||||
|
||||
|
||||
ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
|
||||
|
||||
// act
|
||||
ConfigurationManager.Reset ();
|
||||
ConfigurationManager.Load ();
|
||||
|
||||
// assert
|
||||
Assert.NotEmpty (ConfigurationManager.Themes);
|
||||
Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
|
||||
Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey);
|
||||
Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey);
|
||||
Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey);
|
||||
Assert.False (Application.UseSystemConsole);
|
||||
Assert.False (Application.IsMouseDisabled);
|
||||
Assert.False (Application.HeightAsBuffer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void TestConfigProperties ()
|
||||
{
|
||||
ConfigurationManager.Locations = ConfigLocations.All;
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
Assert.NotEmpty (ConfigurationManager.Settings);
|
||||
// test that all ConfigProperites have our attribute
|
||||
Assert.All (ConfigurationManager.Settings, item => Assert.NotEmpty (item.Value.PropertyInfo.CustomAttributes.Where (a => a.AttributeType == typeof (SerializableConfigurationProperty))));
|
||||
Assert.Empty (ConfigurationManager.Settings.Where (cp => cp.Value.PropertyInfo.GetCustomAttribute (typeof (SerializableConfigurationProperty)) == null));
|
||||
|
||||
// Application is a static class
|
||||
PropertyInfo pi = typeof (Application).GetProperty ("UseSystemConsole");
|
||||
Assert.Equal (pi, ConfigurationManager.Settings ["Application.UseSystemConsole"].PropertyInfo);
|
||||
|
||||
// FrameView is not a static class and DefaultBorderStyle is Scope.Scheme
|
||||
pi = typeof (FrameView).GetProperty ("DefaultBorderStyle");
|
||||
Assert.False (ConfigurationManager.Settings.ContainsKey ("FrameView.DefaultBorderStyle"));
|
||||
Assert.True (ConfigurationManager.Themes ["Default"].ContainsKey ("FrameView.DefaultBorderStyle"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestConfigPropertyOmitClassName ()
|
||||
{
|
||||
// Color.ColorShemes is serialzied as "ColorSchemes", not "Colors.ColorSchemes"
|
||||
PropertyInfo pi = typeof (Colors).GetProperty ("ColorSchemes");
|
||||
var scp = ((SerializableConfigurationProperty)pi.GetCustomAttribute (typeof (SerializableConfigurationProperty)));
|
||||
Assert.True (scp.Scope == typeof (ThemeScope));
|
||||
Assert.True (scp.OmitClassName);
|
||||
|
||||
ConfigurationManager.Reset ();
|
||||
Assert.Equal (pi, ConfigurationManager.Themes ["Default"] ["ColorSchemes"].PropertyInfo);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestConfigurationManagerToJson ()
|
||||
{
|
||||
ConfigurationManager.GetHardCodedDefaults ();
|
||||
var stream = ConfigurationManager.ToStream ();
|
||||
|
||||
|
||||
ConfigurationManager.Settings.Update (stream, "TestConfigurationManagerToJson");
|
||||
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown (configLocation: ConfigLocations.None)]
|
||||
public void TestConfigurationManagerInitDriver_NoLocations ()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown (configLocation: ConfigLocations.DefaultOnly)]
|
||||
public void TestConfigurationManagerInitDriver ()
|
||||
{
|
||||
Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
|
||||
Assert.True (ConfigurationManager.Themes.ContainsKey ("Default"));
|
||||
|
||||
Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey);
|
||||
|
||||
Assert.Equal (Color.White, Colors.ColorSchemes ["Base"].Normal.Foreground);
|
||||
Assert.Equal (Color.Blue, Colors.ColorSchemes ["Base"].Normal.Background);
|
||||
|
||||
// Change Base
|
||||
var json = ConfigurationManager.ToStream ();
|
||||
|
||||
ConfigurationManager.Settings.Update (json, "TestConfigurationManagerInitDriver");
|
||||
|
||||
var colorSchemes = ((Dictionary<string, ColorScheme>)ConfigurationManager.Themes [ConfigurationManager.Themes.Theme] ["ColorSchemes"].PropertyValue);
|
||||
Assert.Equal (Colors.Base, colorSchemes ["Base"]);
|
||||
Assert.Equal (Colors.TopLevel, colorSchemes ["TopLevel"]);
|
||||
Assert.Equal (Colors.Error, colorSchemes ["Error"]);
|
||||
Assert.Equal (Colors.Dialog, colorSchemes ["Dialog"]);
|
||||
Assert.Equal (Colors.Menu, colorSchemes ["Menu"]);
|
||||
|
||||
Colors.Base = colorSchemes ["Base"];
|
||||
Colors.TopLevel = colorSchemes ["TopLevel"];
|
||||
Colors.Error = colorSchemes ["Error"];
|
||||
Colors.Dialog = colorSchemes ["Dialog"];
|
||||
Colors.Menu = colorSchemes ["Menu"];
|
||||
|
||||
Assert.Equal (colorSchemes ["Base"], Colors.Base);
|
||||
Assert.Equal (colorSchemes ["TopLevel"], Colors.TopLevel);
|
||||
Assert.Equal (colorSchemes ["Error"], Colors.Error);
|
||||
Assert.Equal (colorSchemes ["Dialog"], Colors.Dialog);
|
||||
Assert.Equal (colorSchemes ["Menu"], Colors.Menu);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestConfigurationManagerUpdateFromJson ()
|
||||
{
|
||||
// Arrange
|
||||
string json = @"
|
||||
{
|
||||
""$schema"": ""https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json"",
|
||||
""Application.QuitKey"": {
|
||||
""Key"": ""Z"",
|
||||
""Modifiers"": [
|
||||
""Alt""
|
||||
]
|
||||
},
|
||||
""Theme"": ""Default"",
|
||||
""Themes"": [
|
||||
{
|
||||
""Default"": {
|
||||
""ColorSchemes"": [
|
||||
{
|
||||
""TopLevel"": {
|
||||
""Normal"": {
|
||||
""Foreground"": ""BrightGreen"",
|
||||
""Background"": ""Black""
|
||||
},
|
||||
""Focus"": {
|
||||
""Foreground"": ""White"",
|
||||
""Background"": ""Cyan""
|
||||
},
|
||||
""HotNormal"": {
|
||||
""Foreground"": ""Brown"",
|
||||
""Background"": ""Black""
|
||||
},
|
||||
""HotFocus"": {
|
||||
""Foreground"": ""Blue"",
|
||||
""Background"": ""Cyan""
|
||||
},
|
||||
""Disabled"": {
|
||||
""Foreground"": ""DarkGray"",
|
||||
""Background"": ""Black""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
""Base"": {
|
||||
""Normal"": {
|
||||
""Foreground"": ""White"",
|
||||
""Background"": ""Blue""
|
||||
},
|
||||
""Focus"": {
|
||||
""Foreground"": ""Black"",
|
||||
""Background"": ""Gray""
|
||||
},
|
||||
""HotNormal"": {
|
||||
""Foreground"": ""BrightCyan"",
|
||||
""Background"": ""Blue""
|
||||
},
|
||||
""HotFocus"": {
|
||||
""Foreground"": ""BrightBlue"",
|
||||
""Background"": ""Gray""
|
||||
},
|
||||
""Disabled"": {
|
||||
""Foreground"": ""DarkGray"",
|
||||
""Background"": ""Blue""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
""Dialog"": {
|
||||
""Normal"": {
|
||||
""Foreground"": ""Black"",
|
||||
""Background"": ""Gray""
|
||||
},
|
||||
""Focus"": {
|
||||
""Foreground"": ""White"",
|
||||
""Background"": ""DarkGray""
|
||||
},
|
||||
""HotNormal"": {
|
||||
""Foreground"": ""Blue"",
|
||||
""Background"": ""Gray""
|
||||
},
|
||||
""HotFocus"": {
|
||||
""Foreground"": ""BrightYellow"",
|
||||
""Background"": ""DarkGray""
|
||||
},
|
||||
""Disabled"": {
|
||||
""Foreground"": ""Gray"",
|
||||
""Background"": ""DarkGray""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
""Menu"": {
|
||||
""Normal"": {
|
||||
""Foreground"": ""White"",
|
||||
""Background"": ""DarkGray""
|
||||
},
|
||||
""Focus"": {
|
||||
""Foreground"": ""White"",
|
||||
""Background"": ""Black""
|
||||
},
|
||||
""HotNormal"": {
|
||||
""Foreground"": ""BrightYellow"",
|
||||
""Background"": ""DarkGray""
|
||||
},
|
||||
""HotFocus"": {
|
||||
""Foreground"": ""BrightYellow"",
|
||||
""Background"": ""Black""
|
||||
},
|
||||
""Disabled"": {
|
||||
""Foreground"": ""Gray"",
|
||||
""Background"": ""DarkGray""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
""Error"": {
|
||||
""Normal"": {
|
||||
""Foreground"": ""Red"",
|
||||
""Background"": ""White""
|
||||
},
|
||||
""Focus"": {
|
||||
""Foreground"": ""Black"",
|
||||
""Background"": ""BrightRed""
|
||||
},
|
||||
""HotNormal"": {
|
||||
""Foreground"": ""Black"",
|
||||
""Background"": ""White""
|
||||
},
|
||||
""HotFocus"": {
|
||||
""Foreground"": ""White"",
|
||||
""Background"": ""BrightRed""
|
||||
},
|
||||
""Disabled"": {
|
||||
""Foreground"": ""DarkGray"",
|
||||
""Background"": ""White""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
""Dialog.DefaultButtonAlignment"": ""Center""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
";
|
||||
|
||||
ConfigurationManager.Reset ();
|
||||
ConfigurationManager.ThrowOnJsonErrors = true;
|
||||
|
||||
ConfigurationManager.Settings.Update (json, "TestConfigurationManagerUpdateFromJson");
|
||||
|
||||
Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey);
|
||||
Assert.Equal (Key.Z | Key.AltMask, ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue);
|
||||
|
||||
Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
|
||||
|
||||
Assert.Equal (Color.White, Colors.ColorSchemes ["Base"].Normal.Foreground);
|
||||
Assert.Equal (Color.Blue, Colors.ColorSchemes ["Base"].Normal.Background);
|
||||
|
||||
var colorSchemes = (Dictionary<string, ColorScheme>)Themes.First().Value ["ColorSchemes"].PropertyValue;
|
||||
Assert.Equal (Color.White, colorSchemes ["Base"].Normal.Foreground);
|
||||
Assert.Equal (Color.Blue, colorSchemes ["Base"].Normal.Background);
|
||||
|
||||
// Now re-apply
|
||||
ConfigurationManager.Apply ();
|
||||
|
||||
Assert.Equal (Key.Z | Key.AltMask, Application.QuitKey);
|
||||
Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
|
||||
|
||||
Assert.Equal (Color.White, Colors.ColorSchemes ["Base"].Normal.Foreground);
|
||||
Assert.Equal (Color.Blue, Colors.ColorSchemes ["Base"].Normal.Background);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestConfigurationManagerInvalidJsonThrows ()
|
||||
{
|
||||
ConfigurationManager.ThrowOnJsonErrors = true;
|
||||
// "yellow" is not a color
|
||||
string json = @"
|
||||
{
|
||||
""Themes"" : {
|
||||
""ThemeDefinitions"" : [
|
||||
{
|
||||
""Default"" : {
|
||||
""ColorSchemes"": [
|
||||
{
|
||||
""UserDefined"": {
|
||||
""hotNormal"": {
|
||||
""foreground"": ""yellow"",
|
||||
""background"": ""1234""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}";
|
||||
|
||||
JsonException jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
|
||||
Assert.Equal ("Invalid Color: 'yellow'", jsonException.Message);
|
||||
|
||||
// AbNormal is not a ColorScheme attribute
|
||||
json = @"
|
||||
{
|
||||
""Themes"" : {
|
||||
""ThemeDefinitions"" : [
|
||||
{
|
||||
""Default"" : {
|
||||
""ColorSchemes"": [
|
||||
{
|
||||
""UserDefined"": {
|
||||
""AbNormal"": {
|
||||
""foreground"": ""green"",
|
||||
""background"": ""1234""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}";
|
||||
|
||||
jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
|
||||
Assert.Equal ("Unrecognized ColorScheme Attribute name: AbNormal.", jsonException.Message);
|
||||
|
||||
// Modify hotNormal background only
|
||||
json = @"
|
||||
{
|
||||
""Themes"" : {
|
||||
""ThemeDefinitions"" : [
|
||||
{
|
||||
""Default"" : {
|
||||
""ColorSchemes"": [
|
||||
{
|
||||
""UserDefined"": {
|
||||
""hotNormal"": {
|
||||
""background"": ""cyan""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}";
|
||||
|
||||
jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
|
||||
Assert.Equal ("Both Foreground and Background colors must be provided.", jsonException.Message);
|
||||
|
||||
|
||||
// Unknown proeprty
|
||||
json = @"
|
||||
{
|
||||
""Unknown"" : ""Not known""
|
||||
}";
|
||||
|
||||
jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
|
||||
Assert.StartsWith ("Unknown property", jsonException.Message);
|
||||
|
||||
Assert.Equal (0, ConfigurationManager.jsonErrors.Length);
|
||||
|
||||
ConfigurationManager.ThrowOnJsonErrors = false;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestConfigurationManagerInvalidJsonLogs ()
|
||||
{
|
||||
Application.Init (new FakeDriver ());
|
||||
|
||||
ConfigurationManager.ThrowOnJsonErrors = false;
|
||||
// "yellow" is not a color
|
||||
string json = @"
|
||||
{
|
||||
""Themes"" : {
|
||||
""ThemeDefinitions"" : [
|
||||
{
|
||||
""Default"" : {
|
||||
""ColorSchemes"": [
|
||||
{
|
||||
""UserDefined"": {
|
||||
""hotNormal"": {
|
||||
""foreground"": ""yellow"",
|
||||
""background"": ""1234""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}";
|
||||
|
||||
ConfigurationManager.Settings.Update (json, "test");
|
||||
|
||||
// AbNormal is not a ColorScheme attribute
|
||||
json = @"
|
||||
{
|
||||
""Themes"" : {
|
||||
""ThemeDefinitions"" : [
|
||||
{
|
||||
""Default"" : {
|
||||
""ColorSchemes"": [
|
||||
{
|
||||
""UserDefined"": {
|
||||
""AbNormal"": {
|
||||
""foreground"": ""green"",
|
||||
""background"": ""1234""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}";
|
||||
|
||||
ConfigurationManager.Settings.Update (json, "test");
|
||||
|
||||
// Modify hotNormal background only
|
||||
json = @"
|
||||
{
|
||||
""Themes"" : {
|
||||
""ThemeDefinitions"" : [
|
||||
{
|
||||
""Default"" : {
|
||||
""ColorSchemes"": [
|
||||
{
|
||||
""UserDefined"": {
|
||||
""hotNormal"": {
|
||||
""background"": ""cyan""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}";
|
||||
|
||||
ConfigurationManager.Settings.Update (json, "test");
|
||||
|
||||
ConfigurationManager.Settings.Update ("{}}", "test");
|
||||
|
||||
Assert.NotEqual (0, ConfigurationManager.jsonErrors.Length);
|
||||
|
||||
Application.Shutdown ();
|
||||
|
||||
ConfigurationManager.ThrowOnJsonErrors = false;
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void LoadConfigurationFromAllSources_ShouldLoadSettingsFromAllSources ()
|
||||
{
|
||||
//var _configFilename = "config.json";
|
||||
//// Arrange
|
||||
//// Create a mock of the configuration files in all sources
|
||||
//// Home directory
|
||||
//string homeDir = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), ".tui");
|
||||
//if (!Directory.Exists (homeDir)) {
|
||||
// Directory.CreateDirectory (homeDir);
|
||||
//}
|
||||
//string globalConfigFile = Path.Combine (homeDir, _configFilename);
|
||||
//string appSpecificConfigFile = Path.Combine (homeDir, "appname.config.json");
|
||||
//File.WriteAllText (globalConfigFile, "{\"Settings\": {\"TestSetting\":\"Global\"}}");
|
||||
//File.WriteAllText (appSpecificConfigFile, "{\"Settings\": {\"TestSetting\":\"AppSpecific\"}}");
|
||||
|
||||
//// App directory
|
||||
//string appDir = Directory.GetCurrentDirectory ();
|
||||
//string appDirGlobalConfigFile = Path.Combine (appDir, _configFilename);
|
||||
//string appDirAppSpecificConfigFile = Path.Combine (appDir, "appname.config.json");
|
||||
//File.WriteAllText (appDirGlobalConfigFile, "{\"Settings\": {\"TestSetting\":\"GlobalAppDir\"}}");
|
||||
//File.WriteAllText (appDirAppSpecificConfigFile, "{\"Settings\": {\"TestSetting\":\"AppSpecificAppDir\"}}");
|
||||
|
||||
//// App resources
|
||||
//// ...
|
||||
|
||||
//// Act
|
||||
//ConfigurationManager.Locations = ConfigurationManager.ConfigLocation.All;
|
||||
//ConfigurationManager.Load ();
|
||||
|
||||
//// Assert
|
||||
//// Check that the settings from the highest precedence source are loaded
|
||||
//Assert.Equal ("AppSpecific", ConfigurationManager.Config.Settings.TestSetting);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Load_FiresUpdated ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
|
||||
ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
|
||||
ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
|
||||
ConfigurationManager.Settings ["Application.UseSystemConsole"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.HeightAsBuffer"].PropertyValue = true;
|
||||
|
||||
ConfigurationManager.Updated += ConfigurationManager_Updated;
|
||||
bool fired = false;
|
||||
void ConfigurationManager_Updated (ConfigurationManager.ConfigurationManagerEventArgs obj)
|
||||
{
|
||||
fired = true;
|
||||
// assert
|
||||
Assert.Equal (Key.Q | Key.CtrlMask, ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue);
|
||||
Assert.Equal (Key.PageDown | Key.CtrlMask, ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue);
|
||||
Assert.Equal (Key.PageUp | Key.CtrlMask, ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue);
|
||||
Assert.False ((bool)ConfigurationManager.Settings ["Application.UseSystemConsole"].PropertyValue);
|
||||
Assert.False ((bool)ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue);
|
||||
Assert.False ((bool)ConfigurationManager.Settings ["Application.HeightAsBuffer"].PropertyValue);
|
||||
}
|
||||
|
||||
ConfigurationManager.Load (true);
|
||||
|
||||
// assert
|
||||
Assert.True (fired);
|
||||
|
||||
ConfigurationManager.Updated -= ConfigurationManager_Updated;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Apply_FiresApplied ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
ConfigurationManager.Applied += ConfigurationManager_Applied;
|
||||
bool fired = false;
|
||||
void ConfigurationManager_Applied (ConfigurationManager.ConfigurationManagerEventArgs obj)
|
||||
{
|
||||
fired = true;
|
||||
// assert
|
||||
Assert.Equal (Key.Q, Application.QuitKey);
|
||||
Assert.Equal (Key.F, Application.AlternateForwardKey);
|
||||
Assert.Equal (Key.B, Application.AlternateBackwardKey);
|
||||
Assert.True (Application.UseSystemConsole);
|
||||
Assert.True (Application.IsMouseDisabled);
|
||||
Assert.True (Application.HeightAsBuffer);
|
||||
}
|
||||
|
||||
// act
|
||||
ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
|
||||
ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
|
||||
ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
|
||||
ConfigurationManager.Settings ["Application.UseSystemConsole"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.HeightAsBuffer"].PropertyValue = true;
|
||||
|
||||
ConfigurationManager.Apply ();
|
||||
|
||||
// assert
|
||||
Assert.True (fired);
|
||||
|
||||
ConfigurationManager.Applied -= ConfigurationManager_Applied;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
234
UnitTests/Configuration/JsonConverterTests.cs
Normal file
234
UnitTests/Configuration/JsonConverterTests.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using Xunit;
|
||||
using Terminal.Gui.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Terminal.Gui.ConfigurationTests {
|
||||
public class ColorJsonConverterTests {
|
||||
|
||||
[Theory]
|
||||
[InlineData ("Black", Color.Black)]
|
||||
[InlineData ("Blue", Color.Blue)]
|
||||
[InlineData ("BrightBlue", Color.BrightBlue)]
|
||||
[InlineData ("BrightCyan", Color.BrightCyan)]
|
||||
[InlineData ("BrightGreen", Color.BrightGreen)]
|
||||
[InlineData ("BrightMagenta", Color.BrightMagenta)]
|
||||
[InlineData ("BrightRed", Color.BrightRed)]
|
||||
[InlineData ("BrightYellow", Color.BrightYellow)]
|
||||
[InlineData ("Brown", Color.Brown)]
|
||||
[InlineData ("Cyan", Color.Cyan)]
|
||||
[InlineData ("DarkGray", Color.DarkGray)]
|
||||
[InlineData ("Gray", Color.Gray)]
|
||||
[InlineData ("Green", Color.Green)]
|
||||
[InlineData ("Magenta", Color.Magenta)]
|
||||
[InlineData ("Red", Color.Red)]
|
||||
[InlineData ("White", Color.White)]
|
||||
public void TestColorDeserializationFromHumanReadableColorNames (string colorName, Color expectedColor)
|
||||
{
|
||||
// Arrange
|
||||
string json = $"\"{colorName}\"";
|
||||
|
||||
// Act
|
||||
Color actualColor = JsonSerializer.Deserialize<Color> (json, ConfigurationManagerTests._jsonOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedColor, actualColor);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData (Color.Black, "Black")]
|
||||
[InlineData (Color.Blue, "Blue")]
|
||||
[InlineData (Color.Green, "Green")]
|
||||
[InlineData (Color.Cyan, "Cyan")]
|
||||
[InlineData (Color.Gray, "Gray")]
|
||||
[InlineData (Color.Red, "Red")]
|
||||
[InlineData (Color.Magenta, "Magenta")]
|
||||
[InlineData (Color.Brown, "Brown")]
|
||||
[InlineData (Color.DarkGray, "DarkGray")]
|
||||
[InlineData (Color.BrightBlue, "BrightBlue")]
|
||||
[InlineData (Color.BrightGreen, "BrightGreen")]
|
||||
[InlineData (Color.BrightCyan, "BrightCyan")]
|
||||
[InlineData (Color.BrightRed, "BrightRed")]
|
||||
[InlineData (Color.BrightMagenta, "BrightMagenta")]
|
||||
[InlineData (Color.BrightYellow, "BrightYellow")]
|
||||
[InlineData (Color.White, "White")]
|
||||
public void SerializesEnumValuesAsStrings (Color color, string expectedJson)
|
||||
{
|
||||
var converter = new ColorJsonConverter ();
|
||||
var options = new JsonSerializerOptions { Converters = { converter } };
|
||||
|
||||
var serialized = JsonSerializer.Serialize (color, options);
|
||||
|
||||
Assert.Equal ($"\"{expectedJson}\"", serialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSerializeColor_Black ()
|
||||
{
|
||||
// Arrange
|
||||
var color = Color.Black;
|
||||
var expectedJson = "\"Black\"";
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize (color, new JsonSerializerOptions {
|
||||
Converters = { new ColorJsonConverter () }
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedJson, json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSerializeColor_BrightRed ()
|
||||
{
|
||||
// Arrange
|
||||
var color = Color.BrightRed;
|
||||
var expectedJson = "\"BrightRed\"";
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize (color, new JsonSerializerOptions {
|
||||
Converters = { new ColorJsonConverter () }
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedJson, json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDeserializeColor_Black ()
|
||||
{
|
||||
// Arrange
|
||||
var json = "\"Black\"";
|
||||
var expectedColor = Color.Black;
|
||||
|
||||
// Act
|
||||
var color = JsonSerializer.Deserialize<Color> (json, new JsonSerializerOptions {
|
||||
Converters = { new ColorJsonConverter () }
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedColor, color);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDeserializeColor_BrightRed ()
|
||||
{
|
||||
// Arrange
|
||||
var json = "\"BrightRed\"";
|
||||
var expectedColor = Color.BrightRed;
|
||||
|
||||
// Act
|
||||
var color = JsonSerializer.Deserialize<Color> (json, new JsonSerializerOptions {
|
||||
Converters = { new ColorJsonConverter () }
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedColor, color);
|
||||
}
|
||||
}
|
||||
|
||||
public class AttributeJsonConverterTests {
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestDeserialize ()
|
||||
{
|
||||
// Test deserializing from human-readable color names
|
||||
var json = "{\"Foreground\":\"Blue\",\"Background\":\"Green\"}";
|
||||
var attribute = JsonSerializer.Deserialize<Attribute> (json, ConfigurationManagerTests._jsonOptions);
|
||||
Assert.Equal (Color.Blue, attribute.Foreground);
|
||||
Assert.Equal (Color.Green, attribute.Background);
|
||||
|
||||
// Test deserializing from RGB values
|
||||
json = "{\"Foreground\":\"rgb(255,0,0)\",\"Background\":\"rgb(0,255,0)\"}";
|
||||
attribute = JsonSerializer.Deserialize<Attribute> (json, ConfigurationManagerTests._jsonOptions);
|
||||
Assert.Equal (Color.BrightRed, attribute.Foreground);
|
||||
Assert.Equal (Color.BrightGreen, attribute.Background);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestSerialize ()
|
||||
{
|
||||
// Test serializing to human-readable color names
|
||||
var attribute = new Attribute (Color.Blue, Color.Green);
|
||||
var json = JsonSerializer.Serialize<Attribute> (attribute, ConfigurationManagerTests._jsonOptions);
|
||||
Assert.Equal ("{\"Foreground\":\"Blue\",\"Background\":\"Green\"}", json);
|
||||
}
|
||||
}
|
||||
|
||||
public class ColorSchemeJsonConverterTests {
|
||||
//string json = @"
|
||||
// {
|
||||
// ""ColorSchemes"": {
|
||||
// ""Base"": {
|
||||
// ""normal"": {
|
||||
// ""foreground"": ""White"",
|
||||
// ""background"": ""Blue""
|
||||
// },
|
||||
// ""focus"": {
|
||||
// ""foreground"": ""Black"",
|
||||
// ""background"": ""Gray""
|
||||
// },
|
||||
// ""hotNormal"": {
|
||||
// ""foreground"": ""BrightCyan"",
|
||||
// ""background"": ""Blue""
|
||||
// },
|
||||
// ""hotFocus"": {
|
||||
// ""foreground"": ""BrightBlue"",
|
||||
// ""background"": ""Gray""
|
||||
// },
|
||||
// ""disabled"": {
|
||||
// ""foreground"": ""DarkGray"",
|
||||
// ""background"": ""Blue""
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }";
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestColorSchemesSerialization ()
|
||||
{
|
||||
// Arrange
|
||||
var expectedColorScheme = new ColorScheme {
|
||||
Normal = Attribute.Make (Color.White, Color.Blue),
|
||||
Focus = Attribute.Make (Color.Black, Color.Gray),
|
||||
HotNormal = Attribute.Make (Color.BrightCyan, Color.Blue),
|
||||
HotFocus = Attribute.Make (Color.BrightBlue, Color.Gray),
|
||||
Disabled = Attribute.Make (Color.DarkGray, Color.Blue)
|
||||
};
|
||||
var serializedColorScheme = JsonSerializer.Serialize<ColorScheme> (expectedColorScheme, ConfigurationManagerTests._jsonOptions);
|
||||
|
||||
// Act
|
||||
var actualColorScheme = JsonSerializer.Deserialize<ColorScheme> (serializedColorScheme, ConfigurationManagerTests._jsonOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedColorScheme, actualColorScheme);
|
||||
}
|
||||
}
|
||||
|
||||
public class KeyJsonConverterTests {
|
||||
[Theory, AutoInitShutdown]
|
||||
[InlineData (Key.A, "A")]
|
||||
[InlineData (Key.a | Key.ShiftMask, "a, ShiftMask")]
|
||||
[InlineData (Key.A | Key.CtrlMask, "A, CtrlMask")]
|
||||
[InlineData (Key.a | Key.AltMask | Key.CtrlMask, "a, CtrlMask, AltMask")]
|
||||
[InlineData (Key.Delete | Key.AltMask | Key.CtrlMask, "Delete, CtrlMask, AltMask")]
|
||||
[InlineData (Key.D4, "D4")]
|
||||
[InlineData (Key.Esc, "Esc")]
|
||||
public void TestKeyRoundTripConversion (Key key, string expectedStringTo)
|
||||
{
|
||||
// Arrange
|
||||
var options = new JsonSerializerOptions ();
|
||||
options.Converters.Add (new KeyJsonConverter ());
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize (key, options);
|
||||
var deserializedKey = JsonSerializer.Deserialize<Key> (json, options);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedStringTo, deserializedKey.ToString ());
|
||||
}
|
||||
}
|
||||
}
|
||||
92
UnitTests/Configuration/SettingsScopeTests.cs
Normal file
92
UnitTests/Configuration/SettingsScopeTests.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using Xunit;
|
||||
using Terminal.Gui.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui.ConfigurationTests {
|
||||
public class SettingsScopeTests {
|
||||
|
||||
[Fact]
|
||||
public void GetHardCodedDefaults_ShouldSetProperties ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
Assert.Equal (3, ((Dictionary<string, ConfigurationManager.ThemeScope>)ConfigurationManager.Settings ["Themes"].PropertyValue).Count);
|
||||
|
||||
ConfigurationManager.GetHardCodedDefaults ();
|
||||
Assert.NotEmpty (ConfigurationManager.Themes);
|
||||
Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
|
||||
|
||||
Assert.True (ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue is Key);
|
||||
Assert.True (ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue is Key);
|
||||
Assert.True (ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue is Key);
|
||||
Assert.True (ConfigurationManager.Settings ["Application.UseSystemConsole"].PropertyValue is bool);
|
||||
Assert.True (ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue is bool);
|
||||
Assert.True (ConfigurationManager.Settings ["Application.HeightAsBuffer"].PropertyValue is bool);
|
||||
|
||||
Assert.True (ConfigurationManager.Settings ["Theme"].PropertyValue is string);
|
||||
Assert.Equal ("Default", ConfigurationManager.Settings ["Theme"].PropertyValue as string);
|
||||
|
||||
Assert.True (ConfigurationManager.Settings ["Themes"].PropertyValue is Dictionary<string, ConfigurationManager.ThemeScope>);
|
||||
Assert.Single (((Dictionary<string, ConfigurationManager.ThemeScope>)ConfigurationManager.Settings ["Themes"].PropertyValue));
|
||||
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void Apply_ShouldApplyProperties ()
|
||||
{
|
||||
// arrange
|
||||
Assert.Equal (Key.Q | Key.CtrlMask, (Key)ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue);
|
||||
Assert.Equal (Key.PageDown | Key.CtrlMask, (Key)ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue);
|
||||
Assert.Equal (Key.PageUp | Key.CtrlMask, (Key)ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue);
|
||||
Assert.False ((bool)ConfigurationManager.Settings ["Application.UseSystemConsole"].PropertyValue);
|
||||
Assert.False ((bool)ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue);
|
||||
Assert.False ((bool)ConfigurationManager.Settings ["Application.HeightAsBuffer"].PropertyValue);
|
||||
|
||||
// act
|
||||
ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
|
||||
ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
|
||||
ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
|
||||
ConfigurationManager.Settings ["Application.UseSystemConsole"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
|
||||
ConfigurationManager.Settings ["Application.HeightAsBuffer"].PropertyValue = true;
|
||||
|
||||
ConfigurationManager.Settings.Apply ();
|
||||
|
||||
// assert
|
||||
Assert.Equal (Key.Q, Application.QuitKey);
|
||||
Assert.Equal (Key.F, Application.AlternateForwardKey);
|
||||
Assert.Equal (Key.B, Application.AlternateBackwardKey);
|
||||
Assert.True (Application.UseSystemConsole);
|
||||
Assert.True (Application.IsMouseDisabled);
|
||||
Assert.True (Application.HeightAsBuffer);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void CopyUpdatedProperitesFrom_ShouldCopyChangedPropertiesOnly ()
|
||||
{
|
||||
ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.End;
|
||||
|
||||
var updatedSettings = new SettingsScope ();
|
||||
|
||||
///Don't set Quitkey
|
||||
updatedSettings["Application.AlternateForwardKey"].PropertyValue = Key.F;
|
||||
updatedSettings["Application.AlternateBackwardKey"].PropertyValue = Key.B;
|
||||
updatedSettings["Application.UseSystemConsole"].PropertyValue = true;
|
||||
updatedSettings["Application.IsMouseDisabled"].PropertyValue = true;
|
||||
updatedSettings["Application.HeightAsBuffer"].PropertyValue = true;
|
||||
|
||||
ConfigurationManager.Settings.Update (updatedSettings);
|
||||
Assert.Equal (Key.End, ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue);
|
||||
Assert.Equal (Key.F, updatedSettings ["Application.AlternateForwardKey"].PropertyValue);
|
||||
Assert.Equal (Key.B, updatedSettings ["Application.AlternateBackwardKey"].PropertyValue);
|
||||
Assert.True ((bool)updatedSettings ["Application.UseSystemConsole"].PropertyValue);
|
||||
Assert.True ((bool)updatedSettings ["Application.IsMouseDisabled"].PropertyValue);
|
||||
Assert.True ((bool)updatedSettings ["Application.HeightAsBuffer"].PropertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
UnitTests/Configuration/ThemeScopeTests.cs
Normal file
82
UnitTests/Configuration/ThemeScopeTests.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Xunit;
|
||||
using Terminal.Gui.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui.ConfigurationTests {
|
||||
public class ThemeScopeTests {
|
||||
public static readonly JsonSerializerOptions _jsonOptions = new() {
|
||||
Converters = {
|
||||
//new AttributeJsonConverter (),
|
||||
//new ColorJsonConverter ()
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ThemeManager_ClassMethodsWork ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
Assert.Equal (ConfigurationManager.ThemeManager.Instance, ConfigurationManager.Themes);
|
||||
Assert.NotEmpty (ConfigurationManager.ThemeManager.Themes);
|
||||
|
||||
ConfigurationManager.ThemeManager.SelectedTheme = "foo";
|
||||
Assert.Equal ("foo", ConfigurationManager.ThemeManager.SelectedTheme);
|
||||
ConfigurationManager.ThemeManager.Reset ();
|
||||
Assert.Equal (string.Empty, ConfigurationManager.ThemeManager.SelectedTheme);
|
||||
|
||||
Assert.Empty (ConfigurationManager.ThemeManager.Themes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllThemesPresent()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
Assert.True (ConfigurationManager.Themes.ContainsKey ("Default"));
|
||||
Assert.True (ConfigurationManager.Themes.ContainsKey ("Dark"));
|
||||
Assert.True (ConfigurationManager.Themes.ContainsKey ("Light"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHardCodedDefaults_ShouldSetProperties ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
ConfigurationManager.GetHardCodedDefaults ();
|
||||
Assert.NotEmpty (ConfigurationManager.Themes);
|
||||
Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void Apply_ShouldApplyUpdatedProperties ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
Assert.NotEmpty (ConfigurationManager.Themes);
|
||||
Assert.Equal (Dialog.ButtonAlignments.Center, Dialog.DefaultButtonAlignment);
|
||||
|
||||
ConfigurationManager.Themes ["Default"] ["Dialog.DefaultButtonAlignment"].PropertyValue = Dialog.ButtonAlignments.Right;
|
||||
|
||||
ConfigurationManager.ThemeManager.Themes! [ThemeManager.SelectedTheme]!.Apply ();
|
||||
Assert.Equal (Dialog.ButtonAlignments.Right, Dialog.DefaultButtonAlignment);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void TestSerialize_RoundTrip ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
var initial = ConfigurationManager.ThemeManager.Themes;
|
||||
|
||||
var serialized = JsonSerializer.Serialize<IDictionary<string, ThemeScope>> (ConfigurationManager.Themes, _jsonOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<IDictionary<string, ThemeScope>> (serialized, _jsonOptions);
|
||||
|
||||
Assert.NotEqual (initial, deserialized);
|
||||
Assert.Equal (deserialized.Count, initial.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
176
UnitTests/Configuration/ThemeTests.cs
Normal file
176
UnitTests/Configuration/ThemeTests.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using Xunit;
|
||||
using Terminal.Gui.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using static Terminal.Gui.Configuration.ConfigurationManager;
|
||||
|
||||
namespace Terminal.Gui.ConfigurationTests {
|
||||
public class ThemeTests {
|
||||
public static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions () {
|
||||
Converters = {
|
||||
new AttributeJsonConverter (),
|
||||
new ColorJsonConverter ()
|
||||
}
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void TestApply_UpdatesColors ()
|
||||
{
|
||||
// Arrange
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
Assert.False (Colors.ColorSchemes.ContainsKey ("test"));
|
||||
|
||||
var theme = new ThemeScope ();
|
||||
Assert.NotEmpty (theme);
|
||||
|
||||
Themes.Add ("testTheme", theme);
|
||||
|
||||
var colorScheme = new ColorScheme { Normal = new Attribute (Color.Red, Color.Green) };
|
||||
|
||||
theme ["ColorSchemes"].PropertyValue = new Dictionary<string, ColorScheme> () {
|
||||
{ "test", colorScheme }
|
||||
};
|
||||
|
||||
Assert.Equal (Color.Red, ((Dictionary<string, ColorScheme>)theme ["ColorSchemes"].PropertyValue) ["test"].Normal.Foreground);
|
||||
Assert.Equal (Color.Green, ((Dictionary<string, ColorScheme>)theme ["ColorSchemes"].PropertyValue) ["test"].Normal.Background);
|
||||
|
||||
// Act
|
||||
Themes.Theme = "testTheme";
|
||||
Themes! [ThemeManager.SelectedTheme]!.Apply ();
|
||||
|
||||
// Assert
|
||||
var updatedScheme = Colors.ColorSchemes ["test"];
|
||||
Assert.Equal (Color.Red, updatedScheme.Normal.Foreground);
|
||||
Assert.Equal (Color.Green, updatedScheme.Normal.Background);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestApply ()
|
||||
{
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
var theme = new ThemeScope ();
|
||||
Assert.NotEmpty (theme);
|
||||
|
||||
Themes.Add ("testTheme", theme);
|
||||
|
||||
Assert.True (Dialog.DefaultBorder.Effect3D);
|
||||
Assert.Equal (typeof (Border), theme ["Dialog.DefaultBorder"].PropertyInfo.PropertyType);
|
||||
theme ["Dialog.DefaultBorder"].PropertyValue = new Border () { Effect3D = false }; // default is true
|
||||
|
||||
Themes.Theme = "testTheme";
|
||||
Themes! [ThemeManager.SelectedTheme]!.Apply ();
|
||||
|
||||
Assert.False (Dialog.DefaultBorder.Effect3D);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestUpdatFrom_Change ()
|
||||
{
|
||||
// arrange
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
var theme = new ThemeScope ();
|
||||
Assert.NotEmpty (theme);
|
||||
|
||||
var colorScheme = new ColorScheme {
|
||||
// note: ColorScheme's can't be partial; default for each attribute
|
||||
// is always White/Black
|
||||
Normal = new Attribute (Color.Red, Color.Green),
|
||||
Focus = new Attribute (Color.Cyan, Color.BrightCyan),
|
||||
HotNormal = new Attribute (Color.Brown, Color.BrightYellow),
|
||||
HotFocus = new Attribute (Color.Green, Color.BrightGreen),
|
||||
Disabled = new Attribute (Color.Gray, Color.DarkGray),
|
||||
};
|
||||
theme ["ColorSchemes"].PropertyValue = Colors.Create ();
|
||||
((Dictionary<string, ColorScheme>)theme ["ColorSchemes"].PropertyValue) ["test"] = colorScheme;
|
||||
|
||||
var colorSchemes = (Dictionary<string, ColorScheme>)theme ["ColorSchemes"].PropertyValue;
|
||||
Assert.Equal (colorScheme.Normal, colorSchemes ["Test"].Normal);
|
||||
Assert.Equal (colorScheme.Focus, colorSchemes ["Test"].Focus);
|
||||
|
||||
// Change just Normal
|
||||
var newTheme = new ThemeScope ();
|
||||
var newColorScheme = new ColorScheme {
|
||||
Normal = new Attribute (Color.Blue, Color.BrightBlue),
|
||||
|
||||
Focus = colorScheme.Focus,
|
||||
HotNormal =colorScheme.HotNormal,
|
||||
HotFocus = colorScheme.HotFocus,
|
||||
Disabled = colorScheme.Disabled,
|
||||
};
|
||||
newTheme ["ColorSchemes"].PropertyValue = Colors.Create ();
|
||||
((Dictionary<string, ColorScheme>)newTheme ["ColorSchemes"].PropertyValue) ["test"] = newColorScheme;
|
||||
|
||||
// Act
|
||||
theme.Update (newTheme);
|
||||
|
||||
// Assert
|
||||
colorSchemes = (Dictionary<string, ColorScheme>)theme ["ColorSchemes"].PropertyValue;
|
||||
// Normal should have changed
|
||||
Assert.Equal (Color.Blue, colorSchemes ["Test"].Normal.Foreground);
|
||||
Assert.Equal (Color.BrightBlue, colorSchemes ["Test"].Normal.Background);
|
||||
Assert.Equal (Color.Cyan, colorSchemes ["Test"].Focus.Foreground);
|
||||
Assert.Equal (Color.BrightCyan, colorSchemes ["Test"].Focus.Background);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestUpdatFrom_Add ()
|
||||
{
|
||||
// arrange
|
||||
ConfigurationManager.Reset ();
|
||||
|
||||
var theme = new ThemeScope ();
|
||||
Assert.NotEmpty (theme);
|
||||
|
||||
theme ["ColorSchemes"].PropertyValue = Colors.Create ();
|
||||
var colorSchemes = (Dictionary<string, ColorScheme>)theme ["ColorSchemes"].PropertyValue;
|
||||
Assert.Equal (Colors.ColorSchemes.Count, colorSchemes.Count);
|
||||
|
||||
var newTheme = new ThemeScope ();
|
||||
var colorScheme = new ColorScheme {
|
||||
// note: ColorScheme's can't be partial; default for each attribute
|
||||
// is always White/Black
|
||||
Normal = new Attribute (Color.Red, Color.Green),
|
||||
Focus = new Attribute (Color.Cyan, Color.BrightCyan),
|
||||
HotNormal = new Attribute (Color.Brown, Color.BrightYellow),
|
||||
HotFocus = new Attribute (Color.Green, Color.BrightGreen),
|
||||
Disabled = new Attribute (Color.Gray, Color.DarkGray),
|
||||
};
|
||||
|
||||
newTheme ["ColorSchemes"].PropertyValue = Colors.Create ();
|
||||
// add a new ColorScheme to the newTheme
|
||||
((Dictionary<string, ColorScheme>)theme ["ColorSchemes"].PropertyValue) ["test"] = colorScheme;
|
||||
|
||||
colorSchemes = (Dictionary<string, ColorScheme>)theme ["ColorSchemes"].PropertyValue;
|
||||
Assert.Equal (Colors.ColorSchemes.Count + 1, colorSchemes.Count);
|
||||
|
||||
// Act
|
||||
theme.Update (newTheme);
|
||||
|
||||
// Assert
|
||||
colorSchemes = (Dictionary<string, ColorScheme>)theme ["ColorSchemes"].PropertyValue;
|
||||
Assert.Equal (colorSchemes ["Test"].Normal, colorScheme.Normal);
|
||||
Assert.Equal (colorSchemes ["Test"].Focus, colorScheme.Focus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSerialize_RoundTrip ()
|
||||
{
|
||||
var theme = new ThemeScope ();
|
||||
theme ["Dialog.DefaultButtonAlignment"].PropertyValue = Dialog.ButtonAlignments.Right;
|
||||
|
||||
var json = JsonSerializer.Serialize (theme, _jsonOptions);
|
||||
|
||||
var deserialized = JsonSerializer.Deserialize<ThemeScope> (json, _jsonOptions);
|
||||
|
||||
Assert.Equal (Dialog.ButtonAlignments.Right, (Dialog.ButtonAlignments)deserialized ["Dialog.DefaultButtonAlignment"].PropertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Attribute = Terminal.Gui.Attribute;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using Terminal.Gui.Configuration;
|
||||
|
||||
|
||||
// This class enables test functions annotated with the [AutoInitShutdown] attribute to
|
||||
@@ -36,11 +37,14 @@ public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
|
||||
/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
|
||||
/// <param name="fakeClipboardIsSupportedAlwaysTrue">Only valid if <paramref name="autoInit"/> is true.
|
||||
/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
|
||||
/// <param name="configLocation">Determines what config file locations <see cref="ConfigurationManager"/> will
|
||||
/// load from.</param>
|
||||
public AutoInitShutdownAttribute (bool autoInit = true, bool autoShutdown = true,
|
||||
Type consoleDriverType = null,
|
||||
bool useFakeClipboard = false,
|
||||
bool fakeClipboardAlwaysThrowsNotSupportedException = false,
|
||||
bool fakeClipboardIsSupportedAlwaysTrue = false)
|
||||
bool fakeClipboardIsSupportedAlwaysTrue = false,
|
||||
ConfigurationManager.ConfigLocations configLocation = ConfigurationManager.ConfigLocations.DefaultOnly)
|
||||
{
|
||||
//Assert.True (autoInit == false && consoleDriverType == null);
|
||||
|
||||
@@ -50,6 +54,7 @@ public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
|
||||
FakeDriver.FakeBehaviors.UseFakeClipboard = useFakeClipboard;
|
||||
FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
|
||||
FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
|
||||
ConfigurationManager.Locations = configLocation;
|
||||
}
|
||||
|
||||
static bool _init = false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Terminal.Gui;
|
||||
using Xunit;
|
||||
|
||||
namespace Terminal.Gui.TextTests {
|
||||
@@ -339,7 +340,7 @@ namespace Terminal.Gui.TextTests {
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinimizeMovement_True_ShouldStayOnCurrentIfMultipleMatches ()
|
||||
public void MinimizeMovement_True_ShouldStayOnCurrentIfMultipleMatches ()
|
||||
{
|
||||
var strings = new string [] {
|
||||
"$$",
|
||||
|
||||
@@ -645,7 +645,7 @@ namespace Terminal.Gui.TypeTests {
|
||||
};
|
||||
|
||||
Application.Iteration += () => {
|
||||
while (count < 20) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
|
||||
while (count < 20) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
|
||||
|
||||
Application.RequestStop ();
|
||||
};
|
||||
@@ -1088,7 +1088,7 @@ namespace Terminal.Gui.TypeTests {
|
||||
};
|
||||
|
||||
Application.Iteration += () => {
|
||||
while (count > 0) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
|
||||
while (count > 0) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
|
||||
|
||||
Application.RequestStop ();
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Terminal.Gui;
|
||||
using Xunit;
|
||||
|
||||
namespace Terminal.Gui.TypeTests {
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Terminal.Gui.TypeTests {
|
||||
var win = new Window ();
|
||||
|
||||
var label = new Label ("This should be the last line.") {
|
||||
TextAlignment = Terminal.Gui.TextAlignment.Centered,
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
ColorScheme = Colors.Menu,
|
||||
Width = Dim.Fill (),
|
||||
X = Pos.Center (),
|
||||
@@ -173,7 +173,7 @@ namespace Terminal.Gui.TypeTests {
|
||||
var win = new Window ();
|
||||
|
||||
var label = new Label ("This should be the last line.") {
|
||||
TextAlignment = Terminal.Gui.TextAlignment.Centered,
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
ColorScheme = Colors.Menu,
|
||||
Width = Dim.Fill (),
|
||||
X = Pos.Center (),
|
||||
@@ -220,7 +220,7 @@ namespace Terminal.Gui.TypeTests {
|
||||
var win = new Window ();
|
||||
|
||||
var label = new Label ("This should be the last line.") {
|
||||
TextAlignment = Terminal.Gui.TextAlignment.Centered,
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
ColorScheme = Colors.Menu,
|
||||
Width = Dim.Fill (),
|
||||
X = Pos.Center (),
|
||||
@@ -283,7 +283,7 @@ namespace Terminal.Gui.TypeTests {
|
||||
var win = new Window ();
|
||||
|
||||
var label = new Label ("This should be the last line.") {
|
||||
TextAlignment = Terminal.Gui.TextAlignment.Centered,
|
||||
TextAlignment = TextAlignment.Centered,
|
||||
ColorScheme = Colors.Menu,
|
||||
Width = Dim.Fill (),
|
||||
X = Pos.Center (),
|
||||
@@ -891,9 +891,7 @@ namespace Terminal.Gui.TypeTests {
|
||||
};
|
||||
|
||||
Application.Iteration += () => {
|
||||
while (count < 20) {
|
||||
field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
|
||||
}
|
||||
while (count < 20) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
|
||||
|
||||
Application.RequestStop ();
|
||||
};
|
||||
@@ -951,9 +949,7 @@ namespace Terminal.Gui.TypeTests {
|
||||
};
|
||||
|
||||
Application.Iteration += () => {
|
||||
while (count > 0) {
|
||||
field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
|
||||
}
|
||||
while (count > 0) field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
|
||||
|
||||
Application.RequestStop ();
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Terminal.Gui;
|
||||
using Xunit;
|
||||
|
||||
namespace Terminal.Gui.TypeTests {
|
||||
|
||||
@@ -98,6 +98,7 @@ namespace UICatalog.Tests {
|
||||
int stackSize = CreateInput ("");
|
||||
|
||||
Application.Init (new FakeDriver ());
|
||||
Application.QuitKey = Key.CtrlMask | Key.Q; // Config manager may have set this to a different key
|
||||
|
||||
int iterations = 0;
|
||||
Application.Iteration = () => {
|
||||
|
||||
@@ -2096,6 +2096,93 @@ namespace Terminal.Gui.ViewTests {
|
||||
}
|
||||
|
||||
|
||||
[Fact,AutoInitShutdown]
|
||||
public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRebuildForTileCount ()
|
||||
{
|
||||
var tv = GetTileView (20,10);
|
||||
|
||||
var myReusableView = new DisposeCounter ();
|
||||
|
||||
// I want my view in the first tile
|
||||
tv.Tiles.ElementAt (0).ContentView.Add (myReusableView);
|
||||
Assert.Equal (0, myReusableView.DisposalCount);
|
||||
|
||||
// I've changed my mind, I want 3 tiles now
|
||||
tv.RebuildForTileCount (3);
|
||||
|
||||
// but I still want my view in the first tile
|
||||
tv.Tiles.ElementAt (0).ContentView.Add (myReusableView);
|
||||
Assert.Multiple (
|
||||
()=>Assert.Equal (0, myReusableView.DisposalCount)
|
||||
,()=> {
|
||||
tv.Dispose ();
|
||||
Assert.Equal (1, myReusableView.DisposalCount);
|
||||
});
|
||||
}
|
||||
[Fact, AutoInitShutdown]
|
||||
public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringInsertTile ()
|
||||
{
|
||||
var tv = GetTileView (20, 10);
|
||||
|
||||
var myReusableView = new DisposeCounter ();
|
||||
|
||||
// I want my view in the first tile
|
||||
tv.Tiles.ElementAt (0).ContentView.Add (myReusableView);
|
||||
Assert.Equal (0, myReusableView.DisposalCount);
|
||||
|
||||
// I've changed my mind, I want 3 tiles now
|
||||
tv.InsertTile (0);
|
||||
tv.InsertTile (2);
|
||||
|
||||
// but I still want my view in the first tile
|
||||
tv.Tiles.ElementAt (0).ContentView.Add (myReusableView);
|
||||
Assert.Multiple (
|
||||
() => Assert.Equal (0, myReusableView.DisposalCount)
|
||||
, () => {
|
||||
tv.Dispose ();
|
||||
|
||||
// TODO seems to be double disposed ?!
|
||||
Assert.True (myReusableView.DisposalCount >= 1);
|
||||
});
|
||||
}
|
||||
[Theory, AutoInitShutdown]
|
||||
[InlineData(0)]
|
||||
[InlineData (1)]
|
||||
public void TestDisposal_NoEarlyDisposalsOfUsersViews_DuringRemoveTile(int idx)
|
||||
{
|
||||
var tv = GetTileView (20, 10);
|
||||
|
||||
var myReusableView = new DisposeCounter ();
|
||||
|
||||
// I want my view in the first tile
|
||||
tv.Tiles.ElementAt (0).ContentView.Add (myReusableView);
|
||||
Assert.Equal (0, myReusableView.DisposalCount);
|
||||
|
||||
tv.RemoveTile (idx);
|
||||
|
||||
// but I still want my view in the first tile
|
||||
tv.Tiles.ElementAt (0).ContentView.Add (myReusableView);
|
||||
Assert.Multiple (
|
||||
() => Assert.Equal (0, myReusableView.DisposalCount)
|
||||
, () => {
|
||||
tv.Dispose ();
|
||||
|
||||
// TODO seems to be double disposed ?!
|
||||
Assert.True (myReusableView.DisposalCount >= 1);
|
||||
});
|
||||
}
|
||||
|
||||
private class DisposeCounter : View
|
||||
{
|
||||
public int DisposalCount;
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
DisposalCount++;
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a vertical orientation root container with left pane split into
|
||||
/// two (with horizontal splitter line).
|
||||
|
||||
110
docfx/articles/config.md
Normal file
110
docfx/articles/config.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Configuration Management
|
||||
|
||||
Terminal.Gui provides configuration and theme management for Terminal.Gui applications via the [`ConfigurationManager`](~/api/Terminal.Gui/Terminal.Gui.Configuration.
|
||||
|
||||
1) **Settings**. Settings are applied to the [`Application`](~/api/Terminal.Gui/Terminal.Gui.Application.yml) class. Settings are accessed via the `Settings` property of the [`ConfigurationManager`](~/api/Terminal.Gui/Terminal.Gui.Configuration.ConfigurationManager.yml) class.
|
||||
2) **Themes**. Themes are a named collection of settings impacting how applications look. The default theme is named "Default". The built-in configuration stored within the Terminal.Gui library defines two additional themes: "Dark", and "Light". Additional themes can be defined in the configuration files.
|
||||
3) **AppSettings**. AppSettings allow applicaitons to use the [`ConfigurationManager`](~/api/Terminal.Gui/Terminal.Gui.Configuration.ConfigurationManager.yml) to store and retrieve application-specific settings.
|
||||
|
||||
The The [`ConfigurationManager`](~/api/Terminal.Gui/Terminal.Gui.Configuration.ConfigurationManager.yml) will look for configuration files in the `.tui` folder in the user's home directory (e.g. `C:/Users/username/.tui` or `/usr/username/.tui`), the folder where the Terminal.Gui application was launched from (e.g. `./.tui`), or as a resource within the Terminal.Gui application's main assembly.
|
||||
|
||||
Settings that will apply to all applications (global settings) reside in files named config.json. Settings that will apply to a specific Terminal.Gui application reside in files named appname.config.json, where appname is the assembly name of the application (e.g. `UICatalog.config.json`).
|
||||
|
||||
Settings are applied using the following precedence (higher precedence settings overwrite lower precedence settings):
|
||||
|
||||
1. App specific settings found in the users's home directory (`~/.tui/appname.config.json`). -- Highest precedence.
|
||||
|
||||
2. App specific settings found in the directory the app was launched from (`./.tui/appname.config.json`).
|
||||
|
||||
3. App settings in app resources (`Resources/config.json`).
|
||||
|
||||
4. Global settings found in the the user's home directory (`~/.tui/config.json`).
|
||||
|
||||
5. Global settings found in the directory the app was launched from (`./.tui/config.json`).
|
||||
|
||||
6. Default settings defined in the Terminal.Gui assembly -- Lowest precedence.
|
||||
|
||||
The `UI Catalog` application provides an example of how to use the [`ConfigurationManager`](~/api/Terminal.Gui/Terminal.Gui.Configuration.ConfigurationManager.yml) class to load and save configuration files. The `Configuration Editor` scenario provides an editor that allows users to edit the configuration files. UI Catalog also uses a file system watcher to detect changes to the configuration files to tell [`ConfigurationManager`](~/api/Terminal.Gui/Terminal.Gui.Configuration.ConfigurationManager.yml) to reaload them; allowing users to change settings without having to restart the application.
|
||||
|
||||
# What Can Be Configured
|
||||
|
||||
## Settings
|
||||
|
||||
Settings for the [`Application`](~/api/Terminal.Gui/Terminal.Gui.Application.yml) class.
|
||||
* [QuitKey](~/api/Terminal.Gui/Terminal.Gui.Application.yml#QuitKey)
|
||||
* [AlternateForwardKey](~/api/Terminal.Gui/Terminal.Gui.Application.yml#AlternateForwardKey)
|
||||
* [AlternateBackwardKey](~/api/Terminal.Gui/Terminal.Gui.Application.yml#AlternateBackwardKey)
|
||||
* [UseSystemConsole](~/api/Terminal.Gui/Terminal.Gui.Application.yml#UseSystemConsole)
|
||||
* [IsMouseDisabled](~/api/Terminal.Gui/Terminal.Gui.Application.yml#IsMouseDisabled)
|
||||
* [HeightAsBuffer](~/api/Terminal.Gui/Terminal.Gui.Application.yml#HeightAsBuffer)
|
||||
|
||||
## Themes
|
||||
|
||||
A Theme is a named collection of settings that impact the visual style of Terminal.Gui applications. The default theme is named "Default". The built-in configuration stored within the Terminal.Gui library defines two more themes: "Dark", and "Light". Additional themes can be defined in the configuration files.
|
||||
|
||||
The Json property `Theme` defines the name of the theme that will be used. If the theme is not found, the default theme will be used.
|
||||
|
||||
Themes support defining ColorSchemes as well as various default settings for Views. Both the default color schemes and user defined color schemes can be configured. See [ColorSchemes](~/api/Terminal.Gui/Terminal.Gui.Colors.yml) for more information.
|
||||
|
||||
# Example Configuration File
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json",
|
||||
"Application.QuitKey": {
|
||||
"Key": "Esc"
|
||||
},
|
||||
"AppSettings": {
|
||||
"UICatalog.StatusBar": false
|
||||
},
|
||||
"Theme": "UI Catalog Theme",
|
||||
"Themes": [
|
||||
{
|
||||
"UI Catalog Theme": {
|
||||
"ColorSchemes": [
|
||||
{
|
||||
"UI Catalog Scheme": {
|
||||
"Normal": {
|
||||
"Foreground": "White",
|
||||
"Background": "Green"
|
||||
},
|
||||
"Focus": {
|
||||
"Foreground": "Green",
|
||||
"Background": "White"
|
||||
},
|
||||
"HotNormal": {
|
||||
"Foreground": "Blue",
|
||||
"Background": "Green"
|
||||
},
|
||||
"HotFocus": {
|
||||
"Foreground": "BrightRed",
|
||||
"Background": "White"
|
||||
},
|
||||
"Disabled": {
|
||||
"Foreground": "BrightGreen",
|
||||
"Background": "Gray"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"TopLevel": {
|
||||
"Normal": {
|
||||
"Foreground": "DarkGray",
|
||||
"Background": "White"
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Dialog.DefaultEffect3D": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# Configuration File Schema
|
||||
|
||||
Settings are defined in JSON format, according to the schema found here:
|
||||
|
||||
https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
|
||||
@@ -5,5 +5,6 @@
|
||||
* [Keyboard Event Processing](keyboard.md)
|
||||
* [Event Processing and the Application Main Loop](mainloop.md)
|
||||
* [Cross-platform Driver Model](drivers.md)
|
||||
* [Configuration and Theme Manager](config.md)
|
||||
* [TableView Deep Dive](tableview.md)
|
||||
* [TreeView Deep Dive](treeview.md)
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
# Builds the Terminal.gui API documentation using docfx
|
||||
|
||||
dotnet build --configuration Release ../Terminal.sln
|
||||
$prevPwd = $PWD; Set-Location -ErrorAction Stop -LiteralPath $PSScriptRoot
|
||||
|
||||
rm ../docs -Recurse -Force -ErrorAction SilentlyContinue
|
||||
try {
|
||||
$PWD # output the current location
|
||||
|
||||
$env:DOCFX_SOURCE_BRANCH_NAME="main"
|
||||
dotnet build --configuration Release ../Terminal.sln
|
||||
|
||||
docfx --metadata
|
||||
rm ../docs -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
$env:DOCFX_SOURCE_BRANCH_NAME="main"
|
||||
|
||||
docfx --metadata --serve --force
|
||||
}
|
||||
finally {
|
||||
# Restore the previous location.
|
||||
$prevPwd | Set-Location
|
||||
}
|
||||
|
||||
docfx --serve --force
|
||||
@@ -16,7 +16,7 @@
|
||||
"dest": "api/Terminal.Gui",
|
||||
"shouldSkipMarkup": true,
|
||||
"properties": {
|
||||
"TargetFramework": "net6.0"
|
||||
"TargetFramework": "net7.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -35,7 +35,7 @@
|
||||
"dest": "api/UICatalog",
|
||||
"shouldSkipMarkup": false,
|
||||
"properties": {
|
||||
"TargetFramework": "net6.0"
|
||||
"TargetFramework": "net7.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -67,7 +67,8 @@
|
||||
"resource": [
|
||||
{
|
||||
"files": [
|
||||
"images/**"
|
||||
"images/**",
|
||||
"schemas/**"
|
||||
],
|
||||
"exclude": [
|
||||
"obj/**",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 10 MiB After Width: | Height: | Size: 1.9 MiB |
@@ -14,6 +14,7 @@ A toolkit for building rich console apps for .NET, .NET Core, and Mono that work
|
||||
* [Keyboard Event Processing](~/articles/keyboard.md)
|
||||
* [Event Processing and the Application Main Loop](~/articles/mainloop.md)
|
||||
* [Cross-platform Driver Model](~/articles/drivers.md)
|
||||
* [Configuration and Theme Manager](~/articles/config.md)
|
||||
* [TableView Deep Dive](~/articles/tableview.md)
|
||||
* [TreeView Deep Dive](~/articles/treeview.md)
|
||||
|
||||
|
||||
296
docfx/schemas/tui-config-schema.json
Normal file
296
docfx/schemas/tui-config-schema.json
Normal file
@@ -0,0 +1,296 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "The JSON schema for the Terminal.Gui Configuration Manager (https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Application.HeightAsBuffer": {
|
||||
"description": "See HeightAsBuffer API documentation.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"Application.AlternateForwardKey": {
|
||||
"description": "Alternative key for navigating forwards through views. SCtrl+Tab is the primary key.",
|
||||
"$ref": "#/definitions/Key"
|
||||
},
|
||||
"Application.AlternateBackwardKey": {
|
||||
"description": "Alternative key for navigating backwards through views. Shift+Ctrl+Tab is the primary key.",
|
||||
"$ref": "#/definitions/Key"
|
||||
},
|
||||
"Application.QuitKey": {
|
||||
"description": "The key to quit the application. Ctrl+Q is the default.",
|
||||
"$ref": "#/definitions/Key"
|
||||
},
|
||||
"Application.IsMouseDisabled": {
|
||||
"description": "Disable or enable the mouse. The mouse is enabled by default.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"Application.UseSystemConsole": {
|
||||
"description": "If true, forces the use of the System.Console-based (aka NetDriver) driver. The default is false.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"Theme": {
|
||||
"description": "The currently selected theme. The default is 'Default'.",
|
||||
"type": "string"
|
||||
},
|
||||
"Themes": {
|
||||
"description": "An array of Theme objects. Each Theme specifies a set of settings for an application. Set Theme to the name of the active theme.",
|
||||
"type": "array",
|
||||
"properties": {
|
||||
"Themes": {
|
||||
"$ref": "#/definitions/Theme"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/ColorScheme"
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Theme": {
|
||||
"description": "A Theme is a collection of settings that are named.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ColorSchemes": {
|
||||
"description": "The ColorSchemes defined for this Theme.",
|
||||
"$ref": "#/definitions/ColorSchemes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ColorSchemes": {
|
||||
"description": "A list of ColorSchemes. Each ColorScheme specifies a set of Attributes (Foreground & Background).",
|
||||
"type": "array",
|
||||
"properties": {
|
||||
"TopLevel": {
|
||||
"$ref": "#/definitions/ColorScheme"
|
||||
},
|
||||
"Base": {
|
||||
"$ref": "#/definitions/ColorScheme"
|
||||
},
|
||||
"Dialog": {
|
||||
"$ref": "#/definitions/ColorScheme"
|
||||
},
|
||||
"Menu": {
|
||||
"$ref": "#/definitions/ColorScheme"
|
||||
},
|
||||
"Error": {
|
||||
"$ref": "#/definitions/ColorScheme"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/ColorScheme"
|
||||
}
|
||||
},
|
||||
"ColorScheme": {
|
||||
"description": "A Terminal.Gui ColorScheme. Specifies the Foreground & Background colors for modes of an Terminal.Gui app.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Normal": {
|
||||
"description": "The foreground and background color for text when the view is not focused, hot, or disabled.",
|
||||
"$ref": "#/definitions/Attribute"
|
||||
},
|
||||
"Focus": {
|
||||
"description": "The foreground and background color for text when the view has focus.",
|
||||
"$ref": "#/definitions/Attribute"
|
||||
},
|
||||
"HotNormal": {
|
||||
"description": "The foreground and background color for text when the view is highlighted (hot).",
|
||||
"$ref": "#/definitions/Attribute"
|
||||
},
|
||||
"HotFocus": {
|
||||
"description": "The foreground and background color for text when the view is highlighted (hot) and has focus.",
|
||||
"$ref": "#/definitions/Attribute"
|
||||
},
|
||||
"Disabled": {
|
||||
"description": "The foreground and background color for text when the view disabled.",
|
||||
"$ref": "#/definitions/Attribute"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Attribute": {
|
||||
"description": "A Terminal.Gui color attribute. Specifies the Foreground & Background colors for Terminal.Gui output.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Foreground": {
|
||||
"$ref": "#/definitions/Color"
|
||||
},
|
||||
"Background": {
|
||||
"$ref": "#/definitions/Color"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Foreground",
|
||||
"Background"
|
||||
]
|
||||
},
|
||||
"Color": {
|
||||
"description": "One be either one of 16 standard color names or an rgb(r,g,b) tuple.",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "string",
|
||||
"properties": {
|
||||
"color": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Black",
|
||||
"Blue",
|
||||
"Green",
|
||||
"Cyan",
|
||||
"Red",
|
||||
"Magenta",
|
||||
"Brown",
|
||||
"Gray",
|
||||
"DarkGray",
|
||||
"BrightBlue",
|
||||
"BrightGreen",
|
||||
"BrightCyan",
|
||||
"BrightRed",
|
||||
"BrightMagenta",
|
||||
"BrightYellow",
|
||||
"White"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^rgb\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*\\)$"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Key": {
|
||||
"description": "A key pressed on the keyboard.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Key": {
|
||||
"description": "A key name (e.g. A, b, 1, 2, Enter, Esc, F5, etc.) or an integer value (e.g. 65, 66, 67, etc.).",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Null",
|
||||
"Backspace",
|
||||
"Tab",
|
||||
"Enter",
|
||||
"Clear",
|
||||
"Esc",
|
||||
"Space",
|
||||
"D0",
|
||||
"D1",
|
||||
"D2",
|
||||
"D3",
|
||||
"D4",
|
||||
"D5",
|
||||
"D6",
|
||||
"D7",
|
||||
"D8",
|
||||
"D9",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
"k",
|
||||
"l",
|
||||
"m",
|
||||
"n",
|
||||
"o",
|
||||
"p",
|
||||
"q",
|
||||
"r",
|
||||
"s",
|
||||
"t",
|
||||
"u",
|
||||
"v",
|
||||
"w",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"F1",
|
||||
"F2",
|
||||
"F3",
|
||||
"F4",
|
||||
"F5",
|
||||
"F6",
|
||||
"F7",
|
||||
"F8",
|
||||
"F9",
|
||||
"F10",
|
||||
"F11",
|
||||
"F12",
|
||||
"Insert",
|
||||
"Delete",
|
||||
"Home",
|
||||
"End",
|
||||
"PageUp",
|
||||
"PageDown",
|
||||
"Up",
|
||||
"Down",
|
||||
"Left",
|
||||
"Right"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Modifiers": {
|
||||
"description": "A keyboard modifier (e.g. Ctrl, Alt, or Shift).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Ctrl",
|
||||
"Alt",
|
||||
"Shift"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"Key"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user