diff --git a/Terminal.Gui/Configuration/AttributeJsonConverter.cs b/Terminal.Gui/Configuration/AttributeJsonConverter.cs
index 66d085baf3..55bd84575f 100644
--- a/Terminal.Gui/Configuration/AttributeJsonConverter.cs
+++ b/Terminal.Gui/Configuration/AttributeJsonConverter.cs
@@ -1,99 +1,97 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
-using Terminal.Gui;
-namespace Terminal.Gui {
- ///
- /// Json converter fro the class.
- ///
- class AttributeJsonConverter : JsonConverter {
- private static AttributeJsonConverter instance;
+namespace Terminal.Gui;
- ///
- ///
- ///
- public static AttributeJsonConverter Instance {
- get {
- if (instance == null) {
- instance = new AttributeJsonConverter ();
- }
+///
+/// Json converter fro the class.
+///
+class AttributeJsonConverter : JsonConverter {
+ static AttributeJsonConverter _instance;
- return instance;
+ ///
+ ///
+ ///
+ public static AttributeJsonConverter Instance {
+ get {
+ if (_instance == null) {
+ _instance = new AttributeJsonConverter ();
}
+
+ return _instance;
}
+ }
- 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}.");
- }
+ 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 = null;
- Color background = null;
- while (reader.Read ()) {
- if (reader.TokenType == JsonTokenType.EndObject) {
- if (foreground == null || background == null) {
- throw new JsonException ($"Both Foreground and Background colors must be provided.");
- }
- return new Attribute (foreground, background);
+ var attribute = new Attribute ();
+ Color? foreground = null;
+ Color? background = null;
+ while (reader.Read ()) {
+ if (reader.TokenType == JsonTokenType.EndObject) {
+ if (foreground == null || background == null) {
+ throw new JsonException ("Both Foreground and Background colors must be provided.");
}
+ return new Attribute (foreground.Value, background.Value);
+ }
- if (reader.TokenType != JsonTokenType.PropertyName) {
- throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
- }
+ 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 ()}\"";
+ var propertyName = reader.GetString ();
+ reader.Read ();
+ var color = $"\"{reader.GetString ()}\"";
- switch (propertyName.ToLower ()) {
- case "foreground":
- foreground = JsonSerializer.Deserialize (color, options);
- break;
- case "background":
- background = JsonSerializer.Deserialize (color, options);
- break;
- //case "bright":
- //case "bold":
- // attribute.Bright = reader.GetBoolean ();
- // break;
- //case "dim":
- // attribute.Dim = reader.GetBoolean ();
- // break;
- //case "underline":
- // attribute.Underline = reader.GetBoolean ();
- // break;
- //case "blink":
- // attribute.Blink = reader.GetBoolean ();
- // break;
- //case "reverse":
- // attribute.Reverse = reader.GetBoolean ();
- // break;
- //case "hidden":
- // attribute.Hidden = reader.GetBoolean ();
- // break;
- //case "strike-through":
- // attribute.StrikeThrough = reader.GetBoolean ();
- // break;
- default:
- throw new JsonException ($"Unknown Attribute property {propertyName}.");
- }
+ switch (propertyName?.ToLower ()) {
+ case "foreground":
+ foreground = JsonSerializer.Deserialize (color, options);
+ break;
+ case "background":
+ background = JsonSerializer.Deserialize (color, options);
+ break;
+ //case "bright":
+ //case "bold":
+ // attribute.Bright = reader.GetBoolean ();
+ // break;
+ //case "dim":
+ // attribute.Dim = reader.GetBoolean ();
+ // break;
+ //case "underline":
+ // attribute.Underline = reader.GetBoolean ();
+ // break;
+ //case "blink":
+ // attribute.Blink = reader.GetBoolean ();
+ // break;
+ //case "reverse":
+ // attribute.Reverse = reader.GetBoolean ();
+ // break;
+ //case "hidden":
+ // attribute.Hidden = reader.GetBoolean ();
+ // break;
+ //case "strike-through":
+ // attribute.StrikeThrough = reader.GetBoolean ();
+ // break;
+ default:
+ throw new JsonException ($"Unknown Attribute property {propertyName}.");
}
- throw new JsonException ();
- }
-
- public override void Write (Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options)
- {
- writer.WriteStartObject ();
- writer.WritePropertyName (nameof(Attribute.Foreground));
- ColorJsonConverter.Instance.Write (writer, value.Foreground, options);
- writer.WritePropertyName (nameof (Attribute.Background));
- ColorJsonConverter.Instance.Write (writer, value.Background, options);
-
- writer.WriteEndObject ();
}
+ throw new JsonException ();
}
-}
+ public override void Write (Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options)
+ {
+ writer.WriteStartObject ();
+ writer.WritePropertyName (nameof (Attribute.Foreground));
+ ColorJsonConverter.Instance.Write (writer, value.Foreground, options);
+ writer.WritePropertyName (nameof (Attribute.Background));
+ ColorJsonConverter.Instance.Write (writer, value.Background, options);
+
+ writer.WriteEndObject ();
+ }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Configuration/ConfigProperty.cs b/Terminal.Gui/Configuration/ConfigProperty.cs
index 64b59a43ed..7ce4e77371 100644
--- a/Terminal.Gui/Configuration/ConfigProperty.cs
+++ b/Terminal.Gui/Configuration/ConfigProperty.cs
@@ -1,31 +1,41 @@
-using System;
+#nullable enable
+
+using System;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
-#nullable enable
-
namespace Terminal.Gui;
///
-/// Holds a property's value and the that allows
+/// Holds a property's value and the that allows
/// to get and set the property's value.
///
///
-/// Configuration properties must be and
+/// Configuration properties must be and
/// and have the
-/// attribute. If the type of the property requires specialized JSON serialization,
-/// a must be provided using
+/// attribute. If the type of the property requires specialized JSON serialization,
+/// a must be provided using
/// the attribute.
///
public class ConfigProperty {
- private object? propertyValue;
///
/// Describes the property.
///
public PropertyInfo? PropertyInfo { get; set; }
+ ///
+ /// 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 .
+ ///
+ ///
+ /// On , performs a sparse-copy of the new value to the existing value (only copies elements of
+ /// the object that are non-null).
+ ///
+ public object? PropertyValue { get; set; }
+
///
/// Helper to get either the Json property named (specified by [JsonPropertyName(name)]
/// or the actual property name.
@@ -38,22 +48,6 @@ public static string GetJsonPropertyName (PropertyInfo pi)
return jpna?.Name ?? pi.Name;
}
- ///
- /// 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 .
- ///
- ///
- /// On , performs a sparse-copy of the new value to the existing value (only copies elements of
- /// the object that are non-null).
- ///
- public object? PropertyValue {
- get => propertyValue;
- set {
- propertyValue = value;
- }
- }
-
internal object? UpdateValueFrom (object source)
{
if (source == null) {
@@ -61,11 +55,11 @@ public object? PropertyValue {
}
var ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
- if (source.GetType () != PropertyInfo!.PropertyType && (ut != null && source.GetType () != ut)) {
+ 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 = ConfigurationManager.DeepMemberwiseCopy (source, PropertyValue);
+ if (PropertyValue != null) {
+ PropertyValue = DeepMemberwiseCopy (source, PropertyValue);
} else {
PropertyValue = source;
}
@@ -78,10 +72,7 @@ public object? PropertyValue {
/// into .
///
///
- public object? RetrieveValue ()
- {
- return PropertyValue = PropertyInfo!.GetValue (null);
- }
+ public object? RetrieveValue () => PropertyValue = PropertyInfo!.GetValue (null);
///
/// Applies the to the property described by .
@@ -91,12 +82,12 @@ public bool Apply ()
{
if (PropertyValue != null) {
try {
- PropertyInfo?.SetValue (null, ConfigurationManager.DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
+ PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
} catch (TargetInvocationException tie) {
// Check if there is an inner exception
if (tie.InnerException != null) {
// Handle the inner exception separately without catching the outer exception
- Exception innerException = tie.InnerException;
+ var innerException = tie.InnerException;
// Handle the inner exception here
throw new JsonException ($"Error Applying Configuration Change: {innerException.Message}", innerException);
@@ -104,9 +95,10 @@ public bool Apply ()
// Handle the outer exception or rethrow it if needed
throw new JsonException ($"Error Applying Configuration Change: {tie.Message}", tie);
+ } catch (ArgumentException ae) {
+ throw new JsonException ($"Error Applying Configuration Change ({PropertyInfo?.Name}): {ae.Message}", ae);
}
}
return PropertyValue != null;
}
-
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs
index 9510848111..977e81d7c0 100644
--- a/Terminal.Gui/Configuration/ConfigurationManager.cs
+++ b/Terminal.Gui/Configuration/ConfigurationManager.cs
@@ -1,6 +1,5 @@
global using static Terminal.Gui.ConfigurationManager;
global using CM = Terminal.Gui.ConfigurationManager;
-
using System;
using System.Collections;
using System.Collections.Generic;
@@ -12,7 +11,6 @@
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
-using static Terminal.Gui.SpinnerStyle;
#nullable enable
@@ -20,87 +18,124 @@
namespace Terminal.Gui;
///
-/// Provides settings and configuration management for Terminal.Gui applications.
+/// Provides settings and configuration management for Terminal.Gui applications.
///
-/// 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 .tui folder in the user's home directory (e.g. C:/Users/username/.tui,
+/// 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 .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.
+/// within the Terminal.Gui application's main assembly.
///
///
-/// Settings are defined in JSON format, according to this schema:
-/// https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
+/// Settings are defined in JSON format, according to this schema:
+/// https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
///
///
-/// Settings that will apply to all applications (global settings) reside in files named config.json. Settings
+/// 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. Application configuration found in the users's home directory (~/.tui/appname.config.json) -- Highest precedence
+/// 1. Application configuration found in the users's home directory (~/.tui/appname.config.json) -- Highest
+/// precedence
///
///
-/// 2. Application configuration found in the directory the app was launched from (./.tui/appname.config.json).
+/// 2. Application configuration found in the directory the app was launched from (./.tui/appname.config.json).
///
///
-/// 3. Application configuration found in the applications's resources (Resources/config.json).
+/// 3. Application configuration found in the applications's resources (Resources/config.json).
///
///
-/// 4. Global configuration found in the user's home directory (~/.tui/config.json).
+/// 4. Global configuration found in the user's home directory (~/.tui/config.json).
///
///
-/// 5. Global configuration found in the directory the app was launched from (./.tui/config.json).
+/// 5. Global configuration found in the directory the app was launched from (./.tui/config.json).
///
///
-/// 6. Global configuration in Terminal.Gui.dll's resources (Terminal.Gui.Resources.config.json) -- Lowest Precidence.
+/// 6. Global configuration in Terminal.Gui.dll's resources (Terminal.Gui.Resources.config.json) -- Lowest
+/// Precidence.
///
///
-public static partial class ConfigurationManager {
+public static class ConfigurationManager {
+
+ ///
+ /// Describes the location of the configuration files. The constants can be
+ /// combined (bitwise) to specify multiple locations.
+ ///
+ [Flags]
+ public enum ConfigLocations {
+ ///
+ /// No configuration will be loaded.
+ ///
+ ///
+ /// Used for development and testing only. For Terminal,Gui to function properly, at least
+ /// should be set.
+ ///
+ None = 0,
+
+ ///
+ /// Global configuration in Terminal.Gui.dll's resources (Terminal.Gui.Resources.config.json) -- Lowest
+ /// Precedence.
+ ///
+ DefaultOnly,
+
+ ///
+ /// This constant is a combination of all locations
+ ///
+ All = -1
- private static readonly string _configFilename = "config.json";
+ }
+
+ static readonly string _configFilename = "config.json";
- internal static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions {
+ internal static readonly JsonSerializerOptions _serializerOptions = new() {
ReadCommentHandling = JsonCommentHandling.Skip,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = true,
Converters = {
- // We override the standard Rune converter to support specifying Glyphs in
- // a flexible way
- new RuneJsonConverter(),
- // Override Key to support "Ctrl+Q" format.
- new KeyJsonConverter()
- },
+ // We override the standard Rune converter to support specifying Glyphs in
+ // a flexible way
+ new RuneJsonConverter (),
+ // Override Key to support "Ctrl+Q" format.
+ new KeyJsonConverter ()
+ },
// Enables Key to be "Ctrl+Q" vs "Ctrl\u002BQ"
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-};
+ };
///
- /// A dictionary of all properties in the Terminal.Gui project that are decorated with the attribute.
- /// The keys are the property names pre-pended with the class that implements the property (e.g. Application.UseSystemConsole).
+ /// A dictionary of all properties in the Terminal.Gui project that are decorated with the
+ /// attribute.
+ /// The keys are the property names pre-pended with the class that implements the property (e.g.
+ /// Application.UseSystemConsole).
/// The values are instances of which hold the property's value and the
/// that allows to get and set the property's value.
///
///
- /// Is until is called.
+ /// Is until is called.
///
internal static Dictionary? _allConfigProperties;
///
- /// The backing property for .
+ /// The backing property for .
///
///
/// Is until is called. Gets set to a new instance by
/// deserialization (see ).
///
- private static SettingsScope? _settings;
+ static SettingsScope? _settings;
+
+ internal static StringBuilder jsonErrors = new ();
///
- /// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the
+ /// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the
+ ///
/// attribute value.
///
public static SettingsScope? Settings {
@@ -110,9 +145,7 @@ public static SettingsScope? Settings {
}
return _settings;
}
- set {
- _settings = value!;
- }
+ set => _settings = value!;
}
///
@@ -124,15 +157,34 @@ public static SettingsScope? Settings {
///
/// Application-specific configuration settings scope.
///
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true), JsonPropertyName ("AppSettings")]
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] [JsonPropertyName ("AppSettings")]
public static AppScope? AppSettings { get; set; }
///
- /// The set of glyphs used to draw checkboxes, lines, borders, etc...See also .
+ /// The set of glyphs used to draw checkboxes, lines, borders, etc...See also
+ /// .
+ ///
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] [JsonPropertyName ("Glyphs")]
+ public static GlyphDefinitions Glyphs { get; set; } = new ();
+
+ ///
+ /// Gets or sets whether the should throw an exception if it encounters
+ /// an error on deserialization. If (the default), the error is logged and printed to the
+ /// console when is called.
///
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true),
- JsonPropertyName ("Glyphs")]
- public static GlyphDefinitions Glyphs { get; set; } = new GlyphDefinitions ();
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ public static bool? ThrowOnJsonErrors { get; set; } = false;
+
+ ///
+ /// Name of the running application. By default this property is set to the application's assembly name.
+ ///
+ public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
+
+ ///
+ /// Gets and sets the locations where will look for config files.
+ /// The value is .
+ ///
+ public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
///
/// Initializes the internal state of ConfigurationManager. Nominally called once as part of application
@@ -143,13 +195,13 @@ internal static void Initialize ()
_allConfigProperties = new Dictionary ();
_settings = null;
- Dictionary classesWithConfigProps = new Dictionary (StringComparer.InvariantCultureIgnoreCase);
+ var classesWithConfigProps = new Dictionary (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;
+ 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);
@@ -159,11 +211,11 @@ where type.GetProperties ().Any (prop => prop.GetCustomAttribute (typeof (Serial
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) {
+ 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 omitted, JsonPropertyName is allowed.
@@ -186,18 +238,18 @@ from p in enumerable
}
///
- /// Creates a JSON document with the configuration specified.
+ /// Creates a JSON document with the configuration specified.
///
///
internal static string ToJson ()
{
- Debug.WriteLine ($"ConfigurationManager.ToJson()");
- return JsonSerializer.Serialize (Settings!, _serializerOptions);
+ Debug.WriteLine ("ConfigurationManager.ToJson()");
+ return JsonSerializer.Serialize (Settings!, _serializerOptions);
}
internal static Stream ToStream ()
{
- var json = JsonSerializer.Serialize (Settings!, _serializerOptions);
+ var json = JsonSerializer.Serialize (Settings!, _serializerOptions);
// turn it into a stream
var stream = new MemoryStream ();
var writer = new StreamWriter (stream);
@@ -207,16 +259,6 @@ internal static Stream ToStream ()
return stream;
}
- ///
- /// Gets or sets whether the should throw an exception if it encounters
- /// an error on deserialization. If (the default), the error is logged and printed to the
- /// console when is called.
- ///
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
- public static bool? ThrowOnJsonErrors { get; set; } = false;
-
- internal static StringBuilder jsonErrors = new StringBuilder ();
-
internal static void AddJsonError (string error)
{
Debug.WriteLine ($"ConfigurationManager: {error}");
@@ -229,15 +271,12 @@ internal static void AddJsonError (string error)
public static void PrintJsonErrors ()
{
if (jsonErrors.Length > 0) {
- Console.WriteLine ($"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:");
+ Console.WriteLine (@"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:");
Console.WriteLine (jsonErrors.ToString ());
}
}
- private static void ClearJsonErrors ()
- {
- jsonErrors.Clear ();
- }
+ static void ClearJsonErrors () => jsonErrors.Clear ();
///
/// Called when the configuration has been updated from a configuration file. Invokes the
@@ -245,12 +284,12 @@ private static void ClearJsonErrors ()
///
public static void OnUpdated ()
{
- Debug.WriteLine ($"ConfigurationManager.OnApplied()");
+ Debug.WriteLine (@"ConfigurationManager.OnApplied()");
Updated?.Invoke (null, new ConfigurationManagerEventArgs ());
}
///
- /// Event fired when the configuration has been updated from a configuration source.
+ /// Event fired when the configuration has been updated from a configuration source.
/// application.
///
public static event EventHandler? Updated;
@@ -265,9 +304,9 @@ public static void OnUpdated ()
///
public static void Reset ()
{
- Debug.WriteLine ($"ConfigurationManager.Reset()");
+ Debug.WriteLine (@"ConfigurationManager.Reset()");
if (_allConfigProperties == null) {
- ConfigurationManager.Initialize ();
+ Initialize ();
}
ClearJsonErrors ();
@@ -291,15 +330,16 @@ public static void Reset ()
/// is set to .
///
///
- ///
- /// This method is only really useful when using ConfigurationManagerTests
- /// to generate the JSON doc that is embedded into Terminal.Gui (during development).
- ///
- ///
- /// WARNING: The Terminal.Gui.Resources.config.json resource has setting definitions (Themes)
- /// that are NOT generated by this function. If you use this function to regenerate Terminal.Gui.Resources.config.json,
- /// make sure you copy the Theme definitions from the existing Terminal.Gui.Resources.config.json file.
- ///
+ ///
+ /// This method is only really useful when using ConfigurationManagerTests
+ /// to generate the JSON doc that is embedded into Terminal.Gui (during development).
+ ///
+ ///
+ /// WARNING: The Terminal.Gui.Resources.config.json resource has setting definitions (Themes)
+ /// that are NOT generated by this function. If you use this function to regenerate
+ /// Terminal.Gui.Resources.config.json,
+ /// make sure you copy the Theme definitions from the existing Terminal.Gui.Resources.config.json file.
+ ///
///
internal static void GetHardCodedDefaults ()
{
@@ -341,12 +381,12 @@ public static void Apply ()
}
///
- /// Called when an updated configuration has been applied to the
+ /// Called when an updated configuration has been applied to the
/// application. Fires the event.
///
public static void OnApplied ()
{
- Debug.WriteLine ($"ConfigurationManager.OnApplied()");
+ Debug.WriteLine ("ConfigurationManager.OnApplied()");
Applied?.Invoke (null, new ConfigurationManagerEventArgs ());
// TODO: Refactor ConfigurationManager to not use an event handler for this.
@@ -355,62 +395,26 @@ public static void OnApplied ()
}
///
- /// Event fired when an updated configuration has been applied to the
+ /// Event fired when an updated configuration has been applied to the
/// application.
///
public static event EventHandler? Applied;
///
- /// Name of the running application. By default this property is set to the application's assembly name.
- ///
- public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
-
- ///
- /// Describes the location of the configuration files. The constants can be
- /// combined (bitwise) to specify multiple locations.
- ///
- [Flags]
- public enum ConfigLocations {
- ///
- /// No configuration will be loaded.
- ///
- ///
- /// Used for development and testing only. For Terminal,Gui to function properly, at least
- /// should be set.
- ///
- None = 0,
-
- ///
- /// Global configuration in Terminal.Gui.dll's resources (Terminal.Gui.Resources.config.json) -- Lowest Precidence.
- ///
- DefaultOnly,
-
- ///
- /// This constant is a combination of all locations
- ///
- All = -1
-
- }
-
- ///
- /// Gets and sets the locations where will look for config files.
- /// The value is .
- ///
- public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
-
- ///
- /// Loads all settings found in the various configuration storage locations to
+ /// Loads all settings found in the various configuration storage locations to
/// the . Optionally,
/// resets all settings attributed with to the defaults.
///
///
/// Use to cause the loaded settings to be applied to the running application.
///
- /// If the state of will
- /// be reset to the defaults.
+ ///
+ /// If the state of will
+ /// be reset to the defaults.
+ ///
public static void Load (bool reset = false)
{
- Debug.WriteLine ($"ConfigurationManager.Load()");
+ Debug.WriteLine ("ConfigurationManager.Load()");
if (reset) {
Reset ();
@@ -446,16 +450,16 @@ public static string GetEmptyJson ()
{
var emptyScope = new SettingsScope ();
emptyScope.Clear ();
- return JsonSerializer.Serialize (emptyScope, _serializerOptions);
+ return JsonSerializer.Serialize (emptyScope, _serializerOptions);
}
///
/// 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.
+ /// To work around this, we implement a 'deep, memberwise copy' method.
///
///
/// TOOD: When System.Text.Json implements `PopulateObject` revisit
- /// https://github.com/dotnet/corefx/issues/37627
+ /// https://github.com/dotnet/corefx/issues/37627
///
///
///
@@ -488,12 +492,10 @@ public static string GetEmptyJson ()
// Dictionary
if (source.GetType ().IsGenericType && source.GetType ().GetGenericTypeDefinition ().IsAssignableFrom (typeof (Dictionary<,>))) {
foreach (var srcKey in ((IDictionary)source).Keys) {
- if (srcKey is string) {
-
- }
- if (((IDictionary)destination).Contains (srcKey))
+ if (srcKey is string) { }
+ if (((IDictionary)destination).Contains (srcKey)) {
((IDictionary)destination) [srcKey] = DeepMemberwiseCopy (((IDictionary)source) [srcKey], ((IDictionary)destination) [srcKey]);
- else {
+ } else {
((IDictionary)destination).Add (srcKey, ((IDictionary)source) [srcKey]);
}
}
@@ -503,7 +505,7 @@ public static string GetEmptyJson ()
// 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
+ foreach ((var sourceProp, var destProp) in
from sourceProp in sourceProps
where destProps.Any (x => x.Name == sourceProp.Name)
let destProp = destProps.First (x => x.Name == sourceProp.Name)
@@ -513,65 +515,18 @@ where destProp.CanWrite
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);
+ try {
+ if (destVal != null) {
+ // Recurse
+ destProp.SetValue (destination, DeepMemberwiseCopy (sourceVal, destVal));
+ } else {
+ destProp.SetValue (destination, sourceVal);
+ }
+ } catch (ArgumentException e) {
+ throw new JsonException ($"Error Applying Configuration Change: {e.Message}", e);
}
}
}
return destination!;
}
-
- //public class ConfiguraitonLocation
- //{
- // public string Name { get; set; } = string.Empty;
-
- // public string? Path { get; set; }
-
- // public async Task UpdateAsync (Stream stream)
- // {
- // var scope = await JsonSerializer.DeserializeAsync (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
- // }
- //}
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Drawing/Color.cs b/Terminal.Gui/Drawing/Color.cs
index bcab0b27f0..65ded54fa2 100644
--- a/Terminal.Gui/Drawing/Color.cs
+++ b/Terminal.Gui/Drawing/Color.cs
@@ -15,82 +15,100 @@ namespace Terminal.Gui;
/// foreground and background colors in Terminal.Gui apps. Used with .
///
///
-///
-/// These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.
-///
-///
-/// For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured using the
-/// property.
-///
+///
+/// These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.
+///
+///
+/// For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured
+/// using the
+/// property.
+///
///
public enum ColorName {
///
/// The black color. ANSI escape sequence: \u001b[30m.
///
Black,
+
///
/// The blue color. ANSI escape sequence: \u001b[34m.
///
Blue,
+
///
/// The green color. ANSI escape sequence: \u001b[32m.
///
Green,
+
///
/// The cyan color. ANSI escape sequence: \u001b[36m.
///
Cyan,
+
///
/// The red color. ANSI escape sequence: \u001b[31m.
///
Red,
+
///
/// The magenta color. ANSI escape sequence: \u001b[35m.
///
Magenta,
+
///
/// The yellow color (also known as Brown). ANSI escape sequence: \u001b[33m.
///
Yellow,
+
///
/// The gray color (also known as White). ANSI escape sequence: \u001b[37m.
///
Gray,
+
///
/// The dark gray color (also known as Bright Black). ANSI escape sequence: \u001b[30;1m.
///
DarkGray,
+
///
/// The bright blue color. ANSI escape sequence: \u001b[34;1m.
///
BrightBlue,
+
///
/// The bright green color. ANSI escape sequence: \u001b[32;1m.
///
BrightGreen,
+
///
/// The bright cyan color. ANSI escape sequence: \u001b[36;1m.
///
BrightCyan,
+
///
/// The bright red color. ANSI escape sequence: \u001b[31;1m.
///
BrightRed,
+
///
/// The bright magenta color. ANSI escape sequence: \u001b[35;1m.
///
BrightMagenta,
+
///
/// The bright yellow color. ANSI escape sequence: \u001b[33;1m.
///
BrightYellow,
+
///
/// The White color (also known as Bright White). ANSI escape sequence: \u001b[37;1m.
///
White
}
+
///
-/// The 16 foreground color codes used by ANSI Esc sequences for 256 color terminals. Add 10 to these values for background color.
+/// The 16 foreground color codes used by ANSI Esc sequences for 256 color terminals. Add 10 to these values for background
+/// color.
///
public enum AnsiColorCode {
///
@@ -102,69 +120,134 @@ public enum AnsiColorCode {
/// The ANSI color code for Red.
///
RED = 31,
+
///
/// The ANSI color code for Green.
///
GREEN = 32,
+
///
/// The ANSI color code for Yellow.
///
YELLOW = 33,
+
///
/// The ANSI color code for Blue.
///
BLUE = 34,
+
///
/// The ANSI color code for Magenta.
///
MAGENTA = 35,
+
///
/// The ANSI color code for Cyan.
///
CYAN = 36,
+
///
/// The ANSI color code for White.
///
WHITE = 37,
+
///
/// The ANSI color code for Bright Black.
///
BRIGHT_BLACK = 90,
+
///
/// The ANSI color code for Bright Red.
///
BRIGHT_RED = 91,
+
///
/// The ANSI color code for Bright Green.
///
BRIGHT_GREEN = 92,
+
///
/// The ANSI color code for Bright Yellow.
///
BRIGHT_YELLOW = 93,
+
///
/// The ANSI color code for Bright Blue.
///
BRIGHT_BLUE = 94,
+
///
/// The ANSI color code for Bright Magenta.
///
BRIGHT_MAGENTA = 95,
+
///
/// The ANSI color code for Bright Cyan.
///
BRIGHT_CYAN = 96,
+
///
/// The ANSI color code for Bright White.
///
BRIGHT_WHITE = 97
}
+
///
-/// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see ).
-/// Used with .
+/// Represents a 24-bit color. Provides automatic mapping between the legacy 4-bit (16 color) system and 24-bit colors (see
+/// ). Used with .
///
[JsonConverter (typeof (ColorJsonConverter))]
-public class Color : IEquatable {
+public readonly struct Color : IEquatable {
+
+ // TODO: Make this map configurable via ConfigurationManager
+ // TODO: This does not need to be a Dictionary, but can be an 16 element array.
+ ///
+ /// Maps legacy 16-color values to the corresponding 24-bit RGB value.
+ ///
+ internal static ImmutableDictionary _colorToNameMap = new Dictionary {
+ // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
+ // See also: https://en.wikipedia.org/wiki/ANSI_escape_code
+ { new Color (12, 12, 12), ColorName.Black },
+ { new Color (0, 55, 218), ColorName.Blue },
+ { new Color (19, 161, 14), ColorName.Green },
+ { new Color (58, 150, 221), ColorName.Cyan },
+ { new Color (197, 15, 31), ColorName.Red },
+ { new Color (136, 23, 152), ColorName.Magenta },
+ { new Color (128, 64, 32), ColorName.Yellow },
+ { new Color (204, 204, 204), ColorName.Gray },
+ { new Color (118, 118, 118), ColorName.DarkGray },
+ { new Color (59, 120, 255), ColorName.BrightBlue },
+ { new Color (22, 198, 12), ColorName.BrightGreen },
+ { new Color (97, 214, 214), ColorName.BrightCyan },
+ { new Color (231, 72, 86), ColorName.BrightRed },
+ { new Color (180, 0, 158), ColorName.BrightMagenta },
+ { new Color (249, 241, 165), ColorName.BrightYellow },
+ { new Color (242, 242, 242), ColorName.White }
+ }.ToImmutableDictionary ();
+
+
+ ///
+ /// Defines the 16 legacy color names and values that can be used to set the
+ ///
+ internal static ImmutableDictionary _colorNameToAnsiColorMap = new Dictionary {
+ { ColorName.Black, AnsiColorCode.BLACK },
+ { ColorName.Blue, AnsiColorCode.BLUE },
+ { ColorName.Green, AnsiColorCode.GREEN },
+ { ColorName.Cyan, AnsiColorCode.CYAN },
+ { ColorName.Red, AnsiColorCode.RED },
+ { ColorName.Magenta, AnsiColorCode.MAGENTA },
+ { ColorName.Yellow, AnsiColorCode.YELLOW },
+ { ColorName.Gray, AnsiColorCode.WHITE },
+ { ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK },
+ { ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
+ { ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
+ { ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
+ { ColorName.BrightRed, AnsiColorCode.BRIGHT_RED },
+ { ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
+ { ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
+ { ColorName.White, AnsiColorCode.BRIGHT_WHITE }
+ }.ToImmutableDictionary ();
+
///
/// Initializes a new instance of the class.
///
@@ -184,7 +267,13 @@ public Color (int red, int green, int blue, int alpha = 0xFF)
/// Initializes a new instance of the class with an encoded 24-bit color value.
///
/// The encoded 24-bit color value (see ).
- public Color (int rgba) => Rgba = rgba;
+ public Color (int rgba)
+ {
+ A = (byte)(rgba >> 24 & 0xFF);
+ R = (byte)(rgba >> 16 & 0xFF);
+ G = (byte)(rgba >> 8 & 0xFF);
+ B = (byte)(rgba & 0xFF);
+ }
///
/// Initializes a new instance of the color from a legacy 16-color value.
@@ -200,7 +289,8 @@ public Color (ColorName colorName)
}
///
- /// Initializes a new instance of the color from string. See for details.
+ /// Initializes a new instance of the color from string. See
+ /// for details.
///
///
///
@@ -229,15 +319,17 @@ public Color ()
///
/// Red color component.
///
- public int R { get; set; }
+ public int R { get; }
+
///
/// Green color component.
///
- public int G { get; set; }
+ public int G { get; }
+
///
/// Blue color component.
///
- public int B { get; set; }
+ public int B { get; }
///
/// Alpha color component.
@@ -245,7 +337,7 @@ public Color ()
///
/// The Alpha channel is not supported by Terminal.Gui.
///
- public int A { get; set; } = 0xFF; // Not currently supported; here for completeness.
+ public int A { get; } // Not currently supported; here for completeness.
///
/// Gets or sets the color value encoded as ARGB32.
@@ -253,64 +345,8 @@ public Color ()
/// (<see cref="A"/> << 24) | (<see cref="R"/> << 16) | (<see cref="G"/> << 8) | <see cref="B"/>
///
///
- public int Rgba {
- get => A << 24 | R << 16 | G << 8 | B;
- set {
- A = (byte)(value >> 24 & 0xFF);
- R = (byte)(value >> 16 & 0xFF);
- G = (byte)(value >> 8 & 0xFF);
- B = (byte)(value & 0xFF);
- }
- }
-
- // TODO: Make this map configurable via ConfigurationManager
- // TODO: This does not need to be a Dictionary, but can be an 16 element array.
- ///
- /// Maps legacy 16-color values to the corresponding 24-bit RGB value.
- ///
- internal static ImmutableDictionary _colorToNameMap = new Dictionary () {
- // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
- // See also: https://en.wikipedia.org/wiki/ANSI_escape_code
- { new Color (12, 12, 12), ColorName.Black },
- { new Color (0, 55, 218), ColorName.Blue },
- { new Color (19, 161, 14), ColorName.Green },
- { new Color (58, 150, 221), ColorName.Cyan },
- { new Color (197, 15, 31), ColorName.Red },
- { new Color (136, 23, 152), ColorName.Magenta },
- { new Color (128, 64, 32), ColorName.Yellow },
- { new Color (204, 204, 204), ColorName.Gray },
- { new Color (118, 118, 118), ColorName.DarkGray },
- { new Color (59, 120, 255), ColorName.BrightBlue },
- { new Color (22, 198, 12), ColorName.BrightGreen },
- { new Color (97, 214, 214), ColorName.BrightCyan },
- { new Color (231, 72, 86), ColorName.BrightRed },
- { new Color (180, 0, 158), ColorName.BrightMagenta },
- { new Color (249, 241, 165), ColorName.BrightYellow },
- { new Color (242, 242, 242), ColorName.White }
- }.ToImmutableDictionary ();
-
-
- ///
- /// Defines the 16 legacy color names and values that can be used to set the
- ///
- internal static ImmutableDictionary _colorNameToAnsiColorMap = new Dictionary {
- { ColorName.Black, AnsiColorCode.BLACK },
- { ColorName.Blue, AnsiColorCode.BLUE },
- { ColorName.Green, AnsiColorCode.GREEN },
- { ColorName.Cyan, AnsiColorCode.CYAN },
- { ColorName.Red, AnsiColorCode.RED },
- { ColorName.Magenta, AnsiColorCode.MAGENTA },
- { ColorName.Yellow, AnsiColorCode.YELLOW },
- { ColorName.Gray, AnsiColorCode.WHITE },
- { ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK },
- { ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
- { ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
- { ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
- { ColorName.BrightRed, AnsiColorCode.BRIGHT_RED },
- { ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
- { ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
- { ColorName.White, AnsiColorCode.BRIGHT_WHITE }
- }.ToImmutableDictionary ();
+ [JsonIgnore]
+ public int Rgba => A << 24 | R << 16 | G << 8 | B;
///
/// Gets or sets the 24-bit color value for each of the legacy 16-color values.
@@ -332,6 +368,28 @@ public static Dictionary Colors {
}
}
+ ///
+ /// Gets the using a legacy 16-color value.
+ /// will return the closest 16 color match to the true color when no exact value is found.
+ ///
+ ///
+ /// Get returns the of the closest 24-bit color value. Set sets the RGB value using a hard-coded
+ /// map.
+ ///
+ [JsonIgnore]
+ public ColorName ColorName => FindClosestColor (this);
+
+ ///
+ /// Gets the using a legacy 16-color value.
+ /// will return the closest 16 color match to the true color when no exact value is found.
+ ///
+ ///
+ /// Get returns the of the closest 24-bit color value. Set sets the RGB value using a hard-coded
+ /// map.
+ ///
+ [JsonIgnore]
+ public AnsiColorCode AnsiColorCode => _colorNameToAnsiColorMap [ColorName];
+
///
/// Converts a legacy to a 24-bit .
///
@@ -346,10 +404,10 @@ public static Dictionary Colors {
internal static ColorName FindClosestColor (Color inputColor)
{
var closestColor = ColorName.Black; // Default to Black
- double closestDistance = double.MaxValue;
+ var closestDistance = double.MaxValue;
foreach (var colorEntry in _colorToNameMap) {
- double distance = CalculateColorDistance (inputColor, colorEntry.Key);
+ var distance = CalculateColorDistance (inputColor, colorEntry.Key);
if (distance < closestDistance) {
closestDistance = distance;
closestColor = colorEntry.Value;
@@ -362,41 +420,128 @@ internal static ColorName FindClosestColor (Color inputColor)
static double CalculateColorDistance (Color color1, Color color2)
{
// Calculate the Euclidean distance between two colors
- double deltaR = (double)color1.R - (double)color2.R;
- double deltaG = (double)color1.G - (double)color2.G;
- double deltaB = (double)color1.B - (double)color2.B;
+ var deltaR = color1.R - (double)color2.R;
+ var deltaG = color1.G - (double)color2.G;
+ var deltaB = color1.B - (double)color2.B;
return Math.Sqrt (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
}
///
- /// Gets or sets the using a legacy 16-color value.
- /// will return the closest 16 color match to the true color when no exact value is found.
+ /// Converts the provided string to a new instance.
///
+ ///
+ /// The text to analyze. Formats supported are
+ /// "#RGB", "#RRGGBB", "#RGBA", "#RRGGBBAA", "rgb(r,g,b)", "rgb(r,g,b,a)", and any of the
+ /// .
+ ///
+ /// The parsed value.
+ /// A boolean value indicating whether parsing was successful.
///
- /// Get returns the of the closest 24-bit color value. Set sets the RGB value using a hard-coded map.
+ /// While supports the alpha channel , Terminal.Gui does not.
///
- public ColorName ColorName {
- get => FindClosestColor (this);
- set {
+ public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
+ {
+ // empty color
+ if (string.IsNullOrEmpty (text)) {
+ color = new Color ();
+ return false;
+ }
- var c = FromColorName (value);
- R = c.R;
- G = c.G;
- B = c.B;
- A = c.A;
+ // #RRGGBB, #RGB
+ if (text [0] == '#' && text.Length is 7 or 4) {
+ if (text.Length == 7) {
+ var r = Convert.ToInt32 (text.Substring (1, 2), 16);
+ var g = Convert.ToInt32 (text.Substring (3, 2), 16);
+ var b = Convert.ToInt32 (text.Substring (5, 2), 16);
+ color = new Color (r, g, b);
+ } else {
+ var rText = char.ToString (text [1]);
+ var gText = char.ToString (text [2]);
+ var bText = char.ToString (text [3]);
+
+ var r = Convert.ToInt32 (rText + rText, 16);
+ var g = Convert.ToInt32 (gText + gText, 16);
+ var b = Convert.ToInt32 (bText + bText, 16);
+ color = new Color (r, g, b);
+ }
+ return true;
+ }
+
+ // #RRGGBB, #RGBA
+ if (text [0] == '#' && text.Length is 8 or 5) {
+ if (text.Length == 7) {
+ var r = Convert.ToInt32 (text.Substring (1, 2), 16);
+ var g = Convert.ToInt32 (text.Substring (3, 2), 16);
+ var b = Convert.ToInt32 (text.Substring (5, 2), 16);
+ var a = Convert.ToInt32 (text.Substring (7, 2), 16);
+ color = new Color (a, r, g, b);
+ } else {
+ var rText = char.ToString (text [1]);
+ var gText = char.ToString (text [2]);
+ var bText = char.ToString (text [3]);
+ var aText = char.ToString (text [4]);
+
+ var r = Convert.ToInt32 (aText + aText, 16);
+ var g = Convert.ToInt32 (rText + rText, 16);
+ var b = Convert.ToInt32 (gText + gText, 16);
+ var a = Convert.ToInt32 (bText + bText, 16);
+ color = new Color (r, g, b, a);
+ }
+ return true;
+ }
+
+ // rgb(r,g,b)
+ var match = Regex.Match (text, @"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);
+ color = new Color (r, g, b);
+ return true;
+ }
+
+ // rgb(r,g,b,a)
+ match = Regex.Match (text, @"rgb\((\d+),(\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);
+ var a = int.Parse (match.Groups [4].Value);
+ color = new Color (r, g, b, a);
+ return true;
+ }
+
+ if (Enum.TryParse (text, true, out var colorName)) {
+ color = new Color (colorName);
+ return true;
}
+
+ color = new Color ();
+ return false;
}
///
- /// Gets or sets the using a legacy 16-color value.
- /// will return the closest 16 color match to the true color when no exact value is found.
+ /// Converts the color to a string representation.
///
///
- /// Get returns the of the closest 24-bit color value. Set sets the RGB value using a hard-coded map.
+ ///
+ /// If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string.
+ ///
+ ///
+ /// (Alpha channel) is ignored and the returned string will not include it.
+ ///
///
- [JsonIgnore]
- public AnsiColorCode AnsiColorCode => _colorNameToAnsiColorMap [ColorName];
+ ///
+ public override string ToString ()
+ {
+ // If Values has an exact match with a named color (in _colorNames), use that.
+ if (_colorToNameMap.TryGetValue (this, out var colorName)) {
+ return Enum.GetName (typeof (ColorName), colorName);
+ }
+ // Otherwise return as an RGB hex value.
+ return $"#{R:X2}{G:X2}{B:X2}";
+ }
#region Legacy Color Names
///
@@ -408,162 +553,85 @@ public ColorName ColorName {
/// The blue color.
///
public const ColorName Blue = ColorName.Blue;
+
///
/// The green color.
///
public const ColorName Green = ColorName.Green;
+
///
/// The cyan color.
///
public const ColorName Cyan = ColorName.Cyan;
+
///
/// The red color.
///
public const ColorName Red = ColorName.Red;
+
///
/// The magenta color.
///
public const ColorName Magenta = ColorName.Magenta;
+
///
/// The yellow color.
///
public const ColorName Yellow = ColorName.Yellow;
+
///
/// The gray color.
///
public const ColorName Gray = ColorName.Gray;
+
///
/// The dark gray color.
///
public const ColorName DarkGray = ColorName.DarkGray;
+
///
/// The bright bBlue color.
///
public const ColorName BrightBlue = ColorName.BrightBlue;
+
///
/// The bright green color.
///
public const ColorName BrightGreen = ColorName.BrightGreen;
+
///
/// The bright cyan color.
///
public const ColorName BrightCyan = ColorName.BrightCyan;
+
///
/// The bright red color.
///
public const ColorName BrightRed = ColorName.BrightRed;
+
///
/// The bright magenta color.
///
public const ColorName BrightMagenta = ColorName.BrightMagenta;
+
///
/// The bright yellow color.
///
public const ColorName BrightYellow = ColorName.BrightYellow;
+
///
/// The White color.
///
public const ColorName White = ColorName.White;
#endregion
- ///
- /// Converts the provided string to a new instance.
- ///
- /// The text to analyze. Formats supported are
- /// "#RGB", "#RRGGBB", "#RGBA", "#RRGGBBAA", "rgb(r,g,b)", "rgb(r,g,b,a)", and any of the
- /// .
- /// The parsed value.
- /// A boolean value indicating whether parsing was successful.
- ///
- /// While supports the alpha channel , Terminal.Gui does not.
- ///
- public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
- {
- // empty color
- if (text == null || text.Length == 0) {
- color = null;
- return false;
- }
-
- // #RRGGBB, #RGB
- if (text [0] == '#' && text.Length is 7 or 4) {
- if (text.Length == 7) {
- int r = Convert.ToInt32 (text.Substring (1, 2), 16);
- int g = Convert.ToInt32 (text.Substring (3, 2), 16);
- int b = Convert.ToInt32 (text.Substring (5, 2), 16);
- color = new Color (r, g, b);
- } else {
- string rText = char.ToString (text [1]);
- string gText = char.ToString (text [2]);
- string bText = char.ToString (text [3]);
-
- int r = Convert.ToInt32 (rText + rText, 16);
- int g = Convert.ToInt32 (gText + gText, 16);
- int b = Convert.ToInt32 (bText + bText, 16);
- color = new Color (r, g, b);
- }
- return true;
- }
-
- // #RRGGBB, #RGBA
- if (text [0] == '#' && text.Length is 8 or 5) {
- if (text.Length == 7) {
- int r = Convert.ToInt32 (text.Substring (1, 2), 16);
- int g = Convert.ToInt32 (text.Substring (3, 2), 16);
- int b = Convert.ToInt32 (text.Substring (5, 2), 16);
- int a = Convert.ToInt32 (text.Substring (7, 2), 16);
- color = new Color (a, r, g, b);
- } else {
- string rText = char.ToString (text [1]);
- string gText = char.ToString (text [2]);
- string bText = char.ToString (text [3]);
- string aText = char.ToString (text [4]);
-
- int r = Convert.ToInt32 (aText + aText, 16);
- int g = Convert.ToInt32 (rText + rText, 16);
- int b = Convert.ToInt32 (gText + gText, 16);
- int a = Convert.ToInt32 (bText + bText, 16);
- color = new Color (r, g, b, a);
- }
- return true;
- }
-
- // rgb(r,g,b)
- var match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+)\)");
- if (match.Success) {
- int r = int.Parse (match.Groups [1].Value);
- int g = int.Parse (match.Groups [2].Value);
- int b = int.Parse (match.Groups [3].Value);
- color = new Color (r, g, b);
- return true;
- }
-
- // rgb(r,g,b,a)
- match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+),(\d+)\)");
- if (match.Success) {
- int r = int.Parse (match.Groups [1].Value);
- int g = int.Parse (match.Groups [2].Value);
- int b = int.Parse (match.Groups [3].Value);
- int a = int.Parse (match.Groups [4].Value);
- color = new Color (r, g, b, a);
- return true;
- }
-
- if (Enum.TryParse (text, true, out var colorName)) {
- color = new Color (colorName);
- return true;
- }
-
- color = null;
- return false;
- }
-
+ // TODO: Verify implict/explicit are correct for below
#region Operators
///
/// Cast from int.
///
///
- public static implicit operator Color (int rgba) => new Color (rgba);
+ public static implicit operator Color (int rgba) => new (rgba);
///
/// Cast to int.
@@ -575,7 +643,7 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
/// Cast from .
///
///
- public static explicit operator Color (ColorName colorName) => new Color (colorName);
+ public static explicit operator Color (ColorName colorName) => new (colorName);
///
/// Cast to .
@@ -590,18 +658,7 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
///
///
///
- public static bool operator == (Color left, Color right)
- {
- if (left is null && right is null) {
- return true;
- }
-
- if (left is null || right is null) {
- return false;
- }
-
- return left.Equals (right);
- }
+ public static bool operator == (Color left, Color right) => left.Equals (right);
///
@@ -610,18 +667,7 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
///
///
///
- public static bool operator != (Color left, Color right)
- {
- if (left is null && right is null) {
- return false;
- }
-
- if (left is null || right is null) {
- return true;
- }
-
- return !left.Equals (right);
- }
+ public static bool operator != (Color left, Color right) => !left.Equals (right);
///
/// Equality operator for and objects.
@@ -661,50 +707,31 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
///
public bool Equals (Color other) => R == other.R &&
- G == other.G &&
- B == other.B &&
- A == other.A;
+ G == other.G &&
+ B == other.B &&
+ A == other.A;
///
public override int GetHashCode () => HashCode.Combine (R, G, B, A);
#endregion
-
- ///
- /// Converts the color to a string representation.
- ///
- ///
- ///
- /// If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string.
- ///
- ///
- /// (Alpha channel) is ignored and the returned string will not include it.
- ///
- ///
- ///
- public override string ToString ()
- {
- // If Values has an exact match with a named color (in _colorNames), use that.
- if (_colorToNameMap.TryGetValue (this, out var colorName)) {
- return Enum.GetName (typeof (ColorName), colorName);
- }
- // Otherwise return as an RGB hex value.
- return $"#{R:X2}{G:X2}{B:X2}";
- }
}
+
///
-/// Attributes represent how text is styled when displayed in the terminal.
+/// Attributes represent how text is styled when displayed in the terminal.
///
///
-/// provides a platform independent representation of colors (and someday other forms of text styling).
-/// They encode both the foreground and the background color and are used in the
-/// class to define color schemes that can be used in an application.
+/// provides a platform independent representation of colors (and someday other forms of text
+/// styling).
+/// They encode both the foreground and the background color and are used in the
+/// class to define color schemes that can be used in an application.
///
[JsonConverter (typeof (AttributeJsonConverter))]
public readonly struct Attribute : IEquatable {
///
/// Default empty attribute.
///
- public static readonly Attribute Default = new Attribute (Color.White, Color.Black);
+ ///
+ public static readonly Attribute Default = new (Color.White, Color.Black);
///
/// The -specific color value.
@@ -716,23 +743,32 @@ public override string ToString ()
/// The foreground color.
///
[JsonConverter (typeof (ColorJsonConverter))]
- public Color Foreground { get; private init; }
+ public Color Foreground { get; }
///
/// The background color.
///
[JsonConverter (typeof (ColorJsonConverter))]
- public Color Background { get; private init; }
+ public Color Background { get; }
///
- /// Initializes a new instance with default values.
+ /// Initializes a new instance with default values.
///
public Attribute ()
{
PlatformColor = -1;
- var d = Default;
- Foreground = new Color (d.Foreground.ColorName);
- Background = new Color (d.Background.ColorName);
+ Foreground = new Color (Default.Foreground.ColorName);
+ Background = new Color (Default.Background.ColorName);
+ }
+
+ ///
+ /// Initializes a new instance from an existing instance.
+ ///
+ public Attribute (Attribute attr)
+ {
+ PlatformColor = -1;
+ Foreground = new Color (attr.Foreground.ColorName);
+ Background = new Color (attr.Background.ColorName);
}
///
@@ -742,9 +778,8 @@ public Attribute ()
internal Attribute (int platformColor)
{
PlatformColor = platformColor;
- var d = Default;
- Foreground = new Color (d.Foreground.ColorName);
- Background = new Color (d.Background.ColorName);
+ Foreground = new Color (Default.Foreground.ColorName);
+ Background = new Color (Default.Background.ColorName);
}
///
@@ -819,7 +854,7 @@ public Attribute (ColorName foregroundName, Color background) : this (new Color
///
/// Initializes a new instance of the struct
- /// with the same colors for the foreground and background.
+ /// with the same colors for the foreground and background.
///
/// The color.
public Attribute (Color color) : this (color, color) { }
@@ -841,24 +876,25 @@ public Attribute (Color color) : this (color, color) { }
///
public static bool operator != (Attribute left, Attribute right) => !(left == right);
- ///
+ ///
public override bool Equals (object obj) => obj is Attribute other && Equals (other);
- ///
+ ///
public bool Equals (Attribute other) => PlatformColor == other.PlatformColor &&
- Foreground == other.Foreground &&
- Background == other.Background;
+ Foreground == other.Foreground &&
+ Background == other.Background;
- ///
+ ///
public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background);
- ///
+ ///
public override string ToString () =>
// Note, Unit tests are dependent on this format
$"{Foreground},{Background}";
}
+
///
-/// Defines the s for common visible elements in a .
+/// Defines the s for common visible elements in a .
/// Containers such as and use to determine
/// the colors used by sub-views.
///
@@ -867,14 +903,15 @@ public override string ToString () =>
///
[JsonConverter (typeof (ColorSchemeJsonConverter))]
public class ColorScheme : IEquatable {
- Attribute _normal = Attribute.Default;
+ Attribute _disabled = Attribute.Default;
Attribute _focus = Attribute.Default;
- Attribute _hotNormal = Attribute.Default;
Attribute _hotFocus = Attribute.Default;
- Attribute _disabled = Attribute.Default;
+ Attribute _hotNormal = Attribute.Default;
+ Attribute _normal = Attribute.Default;
///
- /// Used by and to track which ColorScheme
+ /// Used by and to
+ /// track which ColorScheme
/// is being accessed.
///
internal string _schemeBeingSet = "";
@@ -888,7 +925,7 @@ public ColorScheme () : this (Attribute.Default) { }
/// Creates a new instance, initialized with the values from .
///
/// The scheme to initialize the new instance with.
- public ColorScheme (ColorScheme scheme) : base ()
+ public ColorScheme (ColorScheme scheme)
{
if (scheme != null) {
_normal = scheme.Normal;
@@ -955,21 +992,21 @@ public Attribute Disabled {
///
/// Compares two objects for equality.
///
- ///
+ ///
/// true if the two objects are equal
- public override bool Equals (object obj) => Equals (obj as ColorScheme);
+ public bool Equals (ColorScheme other) => other != null &&
+ EqualityComparer.Default.Equals (_normal, other._normal) &&
+ EqualityComparer.Default.Equals (_focus, other._focus) &&
+ EqualityComparer.Default.Equals (_hotNormal, other._hotNormal) &&
+ EqualityComparer.Default.Equals (_hotFocus, other._hotFocus) &&
+ EqualityComparer.Default.Equals (_disabled, other._disabled);
///
/// Compares two objects for equality.
///
- ///
+ ///
/// true if the two objects are equal
- public bool Equals (ColorScheme other) => other != null &&
- EqualityComparer.Default.Equals (_normal, other._normal) &&
- EqualityComparer.Default.Equals (_focus, other._focus) &&
- EqualityComparer.Default.Equals (_hotNormal, other._hotNormal) &&
- EqualityComparer.Default.Equals (_hotFocus, other._hotFocus) &&
- EqualityComparer.Default.Equals (_disabled, other._disabled);
+ public override bool Equals (object obj) => Equals (obj as ColorScheme);
///
/// Returns a hashcode for this instance.
@@ -977,7 +1014,7 @@ public bool Equals (ColorScheme other) => other != null &&
/// hashcode for this instance
public override int GetHashCode ()
{
- int hashCode = -1242460230;
+ var hashCode = -1242460230;
hashCode = hashCode * -1521134295 + _normal.GetHashCode ();
hashCode = hashCode * -1521134295 + _focus.GetHashCode ();
hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode ();
@@ -1002,6 +1039,7 @@ public override int GetHashCode ()
/// true if the two objects are not equivalent
public static bool operator != (ColorScheme left, ColorScheme right) => !(left == right);
}
+
///
/// The default s for the application.
///
@@ -1009,38 +1047,17 @@ public override int GetHashCode ()
/// This property can be set in a Theme to change the default for the application.
///
public static class Colors {
- class SchemeNameComparerIgnoreCase : IEqualityComparer {
- public bool Equals (string x, string y)
- {
- if (x != null && y != null) {
- return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase);
- }
- return false;
- }
-
- public int GetHashCode (string obj) => obj.ToLowerInvariant ().GetHashCode ();
- }
static Colors () => ColorSchemes = Create ();
- ///
- /// Creates a new dictionary of new objects.
- ///
- public static Dictionary Create () =>
- // Use reflection to dynamically create the default set of ColorSchemes from the list defined
- // by the class.
- typeof (Colors).GetProperties ()
- .Where (p => p.PropertyType == typeof (ColorScheme))
- .Select (p => new KeyValuePair (p.Name, new ColorScheme ()))
- .ToDictionary (t => t.Key, t => t.Value, new SchemeNameComparerIgnoreCase ());
-
///
/// The application Toplevel color scheme, for the default Toplevel views.
///
///
- ///
- /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["TopLevel"];
- ///
+ ///
+ /// This API will be deprecated in the future. Use instead (e.g.
+ /// edit.ColorScheme = Colors.ColorSchemes["TopLevel"];
+ ///
///
public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); }
@@ -1048,9 +1065,10 @@ public static Dictionary Create () =>
/// The base color scheme, for the default Toplevel views.
///
///
- ///
- /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Base"];
- ///
+ ///
+ /// This API will be deprecated in the future. Use instead (e.g.
+ /// edit.ColorScheme = Colors.ColorSchemes["Base"];
+ ///
///
public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); }
@@ -1058,9 +1076,10 @@ public static Dictionary Create () =>
/// The dialog color scheme, for standard popup dialog boxes
///
///
- ///
- /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Dialog"];
- ///
+ ///
+ /// This API will be deprecated in the future. Use instead (e.g.
+ /// edit.ColorScheme = Colors.ColorSchemes["Dialog"];
+ ///
///
public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); }
@@ -1068,9 +1087,10 @@ public static Dictionary Create () =>
/// The menu bar color
///
///
- ///
- /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Menu"];
- ///
+ ///
+ /// This API will be deprecated in the future. Use instead (e.g.
+ /// edit.ColorScheme = Colors.ColorSchemes["Menu"];
+ ///
///
public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); }
@@ -1078,12 +1098,31 @@ public static Dictionary Create () =>
/// The color scheme for showing errors.
///
///
- ///
- /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Error"];
- ///
+ ///
+ /// This API will be deprecated in the future. Use instead (e.g.
+ /// edit.ColorScheme = Colors.ColorSchemes["Error"];
+ ///
///
public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); }
+ ///
+ /// Provides the defined s.
+ ///
+ [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
+ [JsonConverter (typeof (DictionaryJsonConverter))]
+ public static Dictionary ColorSchemes { get; private set; } // Serialization requires this to have a setter (private set;)
+
+ ///
+ /// Creates a new dictionary of new objects.
+ ///
+ public static Dictionary Create () =>
+ // Use reflection to dynamically create the default set of ColorSchemes from the list defined
+ // by the class.
+ typeof (Colors).GetProperties ()
+ .Where (p => p.PropertyType == typeof (ColorScheme))
+ .Select (p => new KeyValuePair (p.Name, new ColorScheme ()))
+ .ToDictionary (t => t.Key, t => t.Value, new SchemeNameComparerIgnoreCase ());
+
static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) => ColorSchemes [schemeBeingSet];
static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null)
@@ -1092,10 +1131,15 @@ static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string s
colorScheme._schemeBeingSet = schemeBeingSet;
}
- ///
- /// Provides the defined s.
- ///
- [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
- [JsonConverter (typeof (DictionaryJsonConverter))]
- public static Dictionary ColorSchemes { get; private set; }
+ class SchemeNameComparerIgnoreCase : IEqualityComparer {
+ public bool Equals (string x, string y)
+ {
+ if (x != null && y != null) {
+ return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase);
+ }
+ return false;
+ }
+
+ public int GetHashCode (string obj) => obj.ToLowerInvariant ().GetHashCode ();
+ }
}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/FileSystemColorProvider.cs b/Terminal.Gui/Views/FileSystemColorProvider.cs
index 6c78f23012..1196cf315e 100644
--- a/Terminal.Gui/Views/FileSystemColorProvider.cs
+++ b/Terminal.Gui/Views/FileSystemColorProvider.cs
@@ -14,7 +14,7 @@ public class FileSystemColorProvider {
///
///
///
- public Color GetColor (IFileSystemInfo file)
+ public Color? GetColor (IFileSystemInfo file)
{
if (FilenameToColor.ContainsKey (file.Name)) {
return FilenameToColor [file.Name];
@@ -443,8 +443,10 @@ public Color GetColor (IFileSystemInfo file)
private static Color StringToColor (string str)
{
- Color.TryParse (str, out var c);
- return c ?? throw new System.Exception ("Failed to parse Color from " + str);
+ if (!Color.TryParse (str, out var c)) {
+ throw new System.Exception ("Failed to parse Color from " + str);
+ }
+ return c;
}
}
}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/TreeView/TreeNode.cs b/Terminal.Gui/Views/TreeView/TreeNode.cs
index 5ae07ae19c..a8769b6e94 100644
--- a/Terminal.Gui/Views/TreeView/TreeNode.cs
+++ b/Terminal.Gui/Views/TreeView/TreeNode.cs
@@ -1,73 +1,65 @@
using System.Collections.Generic;
-namespace Terminal.Gui {
-
+namespace Terminal.Gui;
+
+///
+/// Interface to implement when you want the regular (non generic)
+/// to automatically determine children for your class (without having to specify
+/// an )
+///
+public interface ITreeNode {
+ ///
+ /// Text to display when rendering the node
+ ///
+ string Text { get; set; }
+
///
- /// Interface to implement when you want the regular (non generic)
- /// to automatically determine children for your class (without having to specify
- /// an )
+ /// The children of your class which should be rendered underneath it when expanded
///
- public interface ITreeNode {
- ///
- /// Text to display when rendering the node
- ///
- string Text { get; set; }
+ ///
+ IList Children { get; }
- ///
- /// The children of your class which should be rendered underneath it when expanded
- ///
- ///
- IList Children { get; }
+ ///
+ /// Optionally allows you to store some custom data/class here.
+ ///
+ object Tag { get; set; }
+}
- ///
- /// Optionally allows you to store some custom data/class here.
- ///
- object Tag { get; set; }
- }
+///
+/// Simple class for representing nodes, use with regular (non generic) .
+///
+public class TreeNode : ITreeNode {
///
- /// Simple class for representing nodes, use with regular (non generic) .
+ /// Initialises a new instance with no
///
- public class TreeNode : ITreeNode {
- ///
- /// Children of the current node
- ///
- ///
- public virtual IList Children { get; set; } = new List ();
+ public TreeNode () { }
- ///
- /// Text to display in tree node for current entry
- ///
- ///
- public virtual string Text { get; set; }
+ ///
+ /// Initialises a new instance and sets starting
+ ///
+ public TreeNode (string text) => Text = text;
- ///
- /// Optionally allows you to store some custom data/class here.
- ///
- public object Tag { get; set; }
+ ///
+ /// Children of the current node
+ ///
+ ///
+ public virtual IList Children { get; set; } = new List ();
- ///
- /// returns
- ///
- ///
- public override string ToString ()
- {
- return Text ?? "Unamed Node";
- }
+ ///
+ /// Text to display in tree node for current entry
+ ///
+ ///
+ public virtual string Text { get; set; }
- ///
- /// Initialises a new instance with no
- ///
- public TreeNode ()
- {
+ ///
+ /// Optionally allows you to store some custom data/class here.
+ ///
+ public object Tag { get; set; }
- }
- ///
- /// Initialises a new instance and sets starting
- ///
- public TreeNode (string text)
- {
- Text = text;
- }
- }
+ ///
+ /// returns
+ ///
+ ///
+ public override string ToString () => Text ?? "Unamed Node";
}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs
index b5ca8f4829..9632c81b86 100644
--- a/Terminal.Gui/Views/TreeView/TreeView.cs
+++ b/Terminal.Gui/Views/TreeView/TreeView.cs
@@ -2,1474 +2,1495 @@
// by phillip.piper@gmail.com). Phillip has explicitly granted permission for his design
// and code to be used in this library under the MIT license.
-using System.Text;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
-using Terminal.Gui;
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+///
+/// Interface for all non generic members of .
+/// See TreeView Deep Dive for more information.
+///
+public interface ITreeView {
///
- /// Interface for all non generic members of .
- ///
- /// See TreeView Deep Dive for more information.
- ///
- public interface ITreeView {
- ///
- /// Contains options for changing how the tree is rendered.
- ///
- TreeStyle Style { get; set; }
-
- ///
- /// Removes all objects from the tree and clears selection.
- ///
- void ClearObjects ();
-
- ///
- /// Sets a flag indicating this view needs to be redisplayed because its state has changed.
- ///
- void SetNeedsDisplay ();
+ /// Contains options for changing how the tree is rendered.
+ ///
+ TreeStyle Style { get; set; }
+
+ ///
+ /// Removes all objects from the tree and clears selection.
+ ///
+ void ClearObjects ();
+
+ ///
+ /// Sets a flag indicating this view needs to be redisplayed because its state has changed.
+ ///
+ void SetNeedsDisplay ();
+}
+
+///
+/// Convenience implementation of generic for any tree were all nodes
+/// implement .
+/// See TreeView Deep Dive for more information.
+///
+public class TreeView : TreeView {
+
+ ///
+ /// Creates a new instance of the tree control with absolute positioning and initialises
+ /// with default based builder.
+ ///
+ public TreeView ()
+ {
+ TreeBuilder = new TreeNodeBuilder ();
+ AspectGetter = o => o == null ? "Null" : o.Text ?? o?.ToString () ?? "Unamed Node";
}
+}
+
+///
+/// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
+/// when expanded using a user defined .
+/// See TreeView Deep Dive for more information.
+///
+public class TreeView : View, ITreeView where T : class {
///
- /// Convenience implementation of generic for any tree were all nodes
- /// implement .
- ///
- /// See TreeView Deep Dive for more information.
+ /// Error message to display when the control is not properly initialized at draw time
+ /// (nodes added but no tree builder set).
///
- public class TreeView : TreeView {
+ public static string NoBuilderError = "ERROR: TreeBuilder Not Set";
- ///
- /// Creates a new instance of the tree control with absolute positioning and initialises
- /// with default based builder.
- ///
- public TreeView ()
- {
- TreeBuilder = new TreeNodeBuilder ();
- AspectGetter = o => o == null ? "Null" : (o.Text ?? o?.ToString () ?? "Unamed Node");
- }
+ ///
+ /// Cached result of
+ ///
+ IReadOnlyCollection> cachedLineMap;
+
+ CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
+
+ ///
+ /// Interface for filtering which lines of the tree are displayed
+ /// e.g. to provide text searching. Defaults to
+ /// (no filtering).
+ ///
+ public ITreeViewFilter Filter = null;
+
+ ///
+ /// Secondary selected regions of tree when is true.
+ ///
+ readonly Stack> multiSelectedRegions = new ();
+
+ KeyCode objectActivationKey = KeyCode.Enter;
+ int scrollOffsetHorizontal;
+ int scrollOffsetVertical;
+
+ ///
+ /// private variable for
+ ///
+ T selectedObject;
+
+ ///
+ /// Creates a new tree view with absolute positioning.
+ /// Use to set set root objects for the tree.
+ /// Children will not be rendered until you set .
+ ///
+ public TreeView ()
+ {
+ CanFocus = true;
+
+ // Things this view knows how to do
+ AddCommand (Command.PageUp, () => {
+ MovePageUp ();
+ return true;
+ });
+ AddCommand (Command.PageDown, () => {
+ MovePageDown ();
+ return true;
+ });
+ AddCommand (Command.PageUpExtend, () => {
+ MovePageUp (true);
+ return true;
+ });
+ AddCommand (Command.PageDownExtend, () => {
+ MovePageDown (true);
+ return true;
+ });
+ AddCommand (Command.Expand, () => {
+ Expand ();
+ return true;
+ });
+ AddCommand (Command.ExpandAll, () => {
+ ExpandAll (SelectedObject);
+ return true;
+ });
+ AddCommand (Command.Collapse, () => {
+ CursorLeft (false);
+ return true;
+ });
+ AddCommand (Command.CollapseAll, () => {
+ CursorLeft (true);
+ return true;
+ });
+ AddCommand (Command.LineUp, () => {
+ AdjustSelection (-1);
+ return true;
+ });
+ AddCommand (Command.LineUpExtend, () => {
+ AdjustSelection (-1, true);
+ return true;
+ });
+ AddCommand (Command.LineUpToFirstBranch, () => {
+ AdjustSelectionToBranchStart ();
+ return true;
+ });
+
+ AddCommand (Command.LineDown, () => {
+ AdjustSelection (1);
+ return true;
+ });
+ AddCommand (Command.LineDownExtend, () => {
+ AdjustSelection (1, true);
+ return true;
+ });
+ AddCommand (Command.LineDownToLastBranch, () => {
+ AdjustSelectionToBranchEnd ();
+ return true;
+ });
+
+ AddCommand (Command.TopHome, () => {
+ GoToFirst ();
+ return true;
+ });
+ AddCommand (Command.BottomEnd, () => {
+ GoToEnd ();
+ return true;
+ });
+ AddCommand (Command.SelectAll, () => {
+ SelectAll ();
+ return true;
+ });
+
+ AddCommand (Command.ScrollUp, () => {
+ ScrollUp ();
+ return true;
+ });
+ AddCommand (Command.ScrollDown, () => {
+ ScrollDown ();
+ return true;
+ });
+ AddCommand (Command.Accept, () => {
+ ActivateSelectedObjectIfAny ();
+ return true;
+ });
+
+ // Default keybindings for this view
+ KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+ KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+ KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend);
+ KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend);
+ KeyBindings.Add (KeyCode.CursorRight, Command.Expand);
+ KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.ExpandAll);
+ KeyBindings.Add (KeyCode.CursorLeft, Command.Collapse);
+ KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.CollapseAll);
+
+ KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+ KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend);
+ KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LineUpToFirstBranch);
+
+ KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+ KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend);
+ KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.LineDownToLastBranch);
+
+ KeyBindings.Add (KeyCode.Home, Command.TopHome);
+ KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+ KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.SelectAll);
+ KeyBindings.Add (ObjectActivationKey, Command.Accept);
}
///
- /// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
- /// when expanded using a user defined .
- ///
- /// See TreeView Deep Dive for more information.
- ///
- public class TreeView : View, ITreeView where T : class {
- private int scrollOffsetVertical;
- private int scrollOffsetHorizontal;
-
- ///
- /// Determines how sub branches of the tree are dynamically built at runtime as the user
- /// expands root nodes.
- ///
- ///
- public ITreeBuilder TreeBuilder { get; set; }
-
- ///
- /// private variable for
- ///
- T selectedObject;
-
- ///
- /// Contains options for changing how the tree is rendered.
- ///
- public TreeStyle Style { get; set; } = new TreeStyle ();
-
- ///
- /// True to allow multiple objects to be selected at once.
- ///
- ///
- public bool MultiSelect { get; set; } = true;
-
- ///
- /// Maximum number of nodes that can be expanded in any given branch.
- ///
- public int MaxDepth { get; set; } = 100;
-
- ///
- /// True makes a letter key press navigate to the next visible branch that begins with
- /// that letter/digit.
- ///
- ///
- public bool AllowLetterBasedNavigation { get; set; } = true;
-
- ///
- /// The currently selected object in the tree. When is true this
- /// is the object at which the cursor is at.
- ///
- public T SelectedObject {
- get => selectedObject;
- set {
- var oldValue = selectedObject;
- selectedObject = value;
-
- if (!ReferenceEquals (oldValue, value)) {
- OnSelectionChanged (new SelectionChangedEventArgs (this, oldValue, value));
- }
- }
- }
+ /// Initialises .Creates a new tree view with absolute
+ /// positioning. Use to set set root
+ /// objects for the tree.
+ ///
+ public TreeView (ITreeBuilder builder) : this () => TreeBuilder = builder;
- ///
- /// This event is raised when an object is activated e.g. by double clicking or
- /// pressing .
- ///
- public event EventHandler> ObjectActivated;
-
- // TODO: Update to use Key instead of KeyCode
- ///
- /// Key which when pressed triggers .
- /// Defaults to Enter.
- ///
- public KeyCode ObjectActivationKey {
- get => objectActivationKey;
- set {
- if (objectActivationKey != value) {
- KeyBindings.Replace (ObjectActivationKey, value);
- objectActivationKey = value;
- }
- }
- }
+ ///
+ /// Determines how sub branches of the tree are dynamically built at runtime as the user
+ /// expands root nodes.
+ ///
+ ///
+ public ITreeBuilder TreeBuilder { get; set; }
- ///
- /// Mouse event to trigger .
- /// Defaults to double click ().
- /// Set to null to disable this feature.
- ///
- ///
- public MouseFlags? ObjectActivationButton { get; set; } = MouseFlags.Button1DoubleClicked;
-
- ///
- /// Delegate for multi colored tree views. Return the to use
- /// for each passed object or null to use the default.
- ///
- public Func ColorGetter { get; set; }
-
- ///
- /// Secondary selected regions of tree when is true.
- ///
- private Stack> multiSelectedRegions = new Stack> ();
-
- ///
- /// Cached result of
- ///
- private IReadOnlyCollection> cachedLineMap;
-
- ///
- /// Error message to display when the control is not properly initialized at draw time
- /// (nodes added but no tree builder set).
- ///
- public static string NoBuilderError = "ERROR: TreeBuilder Not Set";
- private KeyCode objectActivationKey = KeyCode.Enter;
-
- ///
- /// Called when the changes.
- ///
- public event EventHandler> SelectionChanged;
-
- ///
- /// Called once for each visible row during rendering. Can be used
- /// to make last minute changes to color or text rendered
- ///
- public event EventHandler> DrawLine;
-
- ///
- /// The root objects in the tree, note that this collection is of root objects only.
- ///
- public IEnumerable Objects { get => roots.Keys; }
-
- ///
- /// Map of root objects to the branches under them. All objects have
- /// a even if that branch has no children.
- ///
- internal Dictionary> roots { get; set; } = new Dictionary> ();
-
- ///
- /// The amount of tree view that has been scrolled off the top of the screen (by the user
- /// scrolling down).
- ///
- /// Setting a value of less than 0 will result in a offset of 0. To see changes
- /// in the UI call .
- public int ScrollOffsetVertical {
- get => scrollOffsetVertical;
- set {
- scrollOffsetVertical = Math.Max (0, value);
- }
- }
+ ///
+ /// True to allow multiple objects to be selected at once.
+ ///
+ ///
+ public bool MultiSelect { get; set; } = true;
+
+ ///
+ /// Maximum number of nodes that can be expanded in any given branch.
+ ///
+ public int MaxDepth { get; set; } = 100;
+
+ ///
+ /// True makes a letter key press navigate to the next visible branch that begins with
+ /// that letter/digit.
+ ///
+ ///
+ public bool AllowLetterBasedNavigation { get; set; } = true;
+
+ ///
+ /// The currently selected object in the tree. When is true this
+ /// is the object at which the cursor is at.
+ ///
+ public T SelectedObject {
+ get => selectedObject;
+ set {
+ var oldValue = selectedObject;
+ selectedObject = value;
- ///
- /// The amount of tree view that has been scrolled to the right (horizontally).
- ///
- /// Setting a value of less than 0 will result in a offset of 0. To see changes
- /// in the UI call .
- public int ScrollOffsetHorizontal {
- get => scrollOffsetHorizontal;
- set {
- scrollOffsetHorizontal = Math.Max (0, value);
+ if (!ReferenceEquals (oldValue, value)) {
+ OnSelectionChanged (new SelectionChangedEventArgs (this, oldValue, value));
}
}
+ }
- ///
- /// The current number of rows in the tree (ignoring the controls bounds).
- ///
- public int ContentHeight => BuildLineMap ().Count ();
-
- ///
- /// Returns the string representation of model objects hosted in the tree. Default
- /// implementation is to call .
- ///
- ///
- public AspectGetterDelegate AspectGetter { get; set; } = (o) => o.ToString () ?? "";
-
- CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
-
- ///
- /// Interface for filtering which lines of the tree are displayed
- /// e.g. to provide text searching. Defaults to
- /// (no filtering).
- ///
- public ITreeViewFilter Filter = null;
-
- ///
- /// Get / Set the wished cursor when the tree is focused.
- /// Only applies when is true.
- /// Defaults to .
- ///
- public CursorVisibility DesiredCursorVisibility {
- get {
- return MultiSelect ? desiredCursorVisibility : CursorVisibility.Invisible;
- }
- set {
- if (desiredCursorVisibility != value) {
- desiredCursorVisibility = value;
- if (HasFocus) {
- Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
- }
- }
+ // TODO: Update to use Key instead of KeyCode
+ ///
+ /// Key which when pressed triggers .
+ /// Defaults to Enter.
+ ///
+ public KeyCode ObjectActivationKey {
+ get => objectActivationKey;
+ set {
+ if (objectActivationKey != value) {
+ KeyBindings.Replace (ObjectActivationKey, value);
+ objectActivationKey = value;
}
}
+ }
- ///
- /// Creates a new tree view with absolute positioning.
- /// Use to set set root objects for the tree.
- /// Children will not be rendered until you set .
- ///
- public TreeView () : base ()
- {
- CanFocus = true;
-
- // Things this view knows how to do
- AddCommand (Command.PageUp, () => { MovePageUp (false); return true; });
- AddCommand (Command.PageDown, () => { MovePageDown (false); return true; });
- AddCommand (Command.PageUpExtend, () => { MovePageUp (true); return true; });
- AddCommand (Command.PageDownExtend, () => { MovePageDown (true); return true; });
- AddCommand (Command.Expand, () => { Expand (); return true; });
- AddCommand (Command.ExpandAll, () => { ExpandAll (SelectedObject); return true; });
- AddCommand (Command.Collapse, () => { CursorLeft (false); return true; });
- AddCommand (Command.CollapseAll, () => { CursorLeft (true); return true; });
- AddCommand (Command.LineUp, () => { AdjustSelection (-1, false); return true; });
- AddCommand (Command.LineUpExtend, () => { AdjustSelection (-1, true); return true; });
- AddCommand (Command.LineUpToFirstBranch, () => { AdjustSelectionToBranchStart (); return true; });
-
- AddCommand (Command.LineDown, () => { AdjustSelection (1, false); return true; });
- AddCommand (Command.LineDownExtend, () => { AdjustSelection (1, true); return true; });
- AddCommand (Command.LineDownToLastBranch, () => { AdjustSelectionToBranchEnd (); return true; });
-
- AddCommand (Command.TopHome, () => { GoToFirst (); return true; });
- AddCommand (Command.BottomEnd, () => { GoToEnd (); return true; });
- AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
-
- AddCommand (Command.ScrollUp, () => { ScrollUp (); return true; });
- AddCommand (Command.ScrollDown, () => { ScrollDown (); return true; });
- AddCommand (Command.Accept, () => { ActivateSelectedObjectIfAny (); return true; });
-
- // Default keybindings for this view
- KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
- KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
- KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend);
- KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend);
- KeyBindings.Add (KeyCode.CursorRight, Command.Expand);
- KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.ExpandAll);
- KeyBindings.Add (KeyCode.CursorLeft, Command.Collapse);
- KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.CollapseAll);
-
- KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
- KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend);
- KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LineUpToFirstBranch);
-
- KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
- KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend);
- KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.LineDownToLastBranch);
-
- KeyBindings.Add (KeyCode.Home, Command.TopHome);
- KeyBindings.Add (KeyCode.End, Command.BottomEnd);
- KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.SelectAll);
- KeyBindings.Add (ObjectActivationKey, Command.Accept);
- }
-
- ///
- /// Initialises .Creates a new tree view with absolute
- /// positioning. Use to set set root
- /// objects for the tree.
- ///
- public TreeView (ITreeBuilder builder) : this ()
- {
- TreeBuilder = builder;
- }
-
- ///
- public override bool OnEnter (View view)
- {
- Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
-
- if (SelectedObject == null && Objects.Any ()) {
- SelectedObject = Objects.First ();
- }
+ ///
+ /// Mouse event to trigger .
+ /// Defaults to double click ().
+ /// Set to null to disable this feature.
+ ///
+ ///
+ public MouseFlags? ObjectActivationButton { get; set; } = MouseFlags.Button1DoubleClicked;
- return base.OnEnter (view);
- }
+ ///
+ /// Delegate for multi colored tree views. Return the to use
+ /// for each passed object or null to use the default.
+ ///
+ public Func ColorGetter { get; set; }
- ///
- /// Adds a new root level object unless it is already a root of the tree.
- ///
- ///
- public void AddObject (T o)
- {
- if (!roots.ContainsKey (o)) {
- roots.Add (o, new Branch (this, null, o));
- InvalidateLineMap ();
- SetNeedsDisplay ();
- }
- }
+ ///
+ /// The root objects in the tree, note that this collection is of root objects only.
+ ///
+ public IEnumerable Objects => roots.Keys;
- ///
- /// Removes all objects from the tree and clears .
- ///
- public void ClearObjects ()
- {
- SelectedObject = default (T);
- multiSelectedRegions.Clear ();
- roots = new Dictionary> ();
- InvalidateLineMap ();
- SetNeedsDisplay ();
- }
+ ///
+ /// Map of root objects to the branches under them. All objects have
+ /// a even if that branch has no children.
+ ///
+ internal Dictionary> roots { get; set; } = new ();
- ///
- /// Removes the given root object from the tree
- ///
- /// If is the currently then the
- /// selection is cleared.
- ///
- public void Remove (T o)
- {
- if (roots.ContainsKey (o)) {
- roots.Remove (o);
- InvalidateLineMap ();
- SetNeedsDisplay ();
+ ///
+ /// The amount of tree view that has been scrolled off the top of the screen (by the user
+ /// scrolling down).
+ ///
+ ///
+ /// Setting a value of less than 0 will result in a offset of 0. To see changes
+ /// in the UI call .
+ ///
+ public int ScrollOffsetVertical {
+ get => scrollOffsetVertical;
+ set => scrollOffsetVertical = Math.Max (0, value);
+ }
+
+ ///
+ /// The amount of tree view that has been scrolled to the right (horizontally).
+ ///
+ ///
+ /// Setting a value of less than 0 will result in a offset of 0. To see changes
+ /// in the UI call .
+ ///
+ public int ScrollOffsetHorizontal {
+ get => scrollOffsetHorizontal;
+ set => scrollOffsetHorizontal = Math.Max (0, value);
+ }
+
+ ///
+ /// The current number of rows in the tree (ignoring the controls bounds).
+ ///
+ public int ContentHeight => BuildLineMap ().Count ();
+
+ ///
+ /// Returns the string representation of model objects hosted in the tree. Default
+ /// implementation is to call .
+ ///
+ ///
+ public AspectGetterDelegate AspectGetter { get; set; } = o => o.ToString () ?? "";
- if (Equals (SelectedObject, o)) {
- SelectedObject = default (T);
+ ///
+ /// Get / Set the wished cursor when the tree is focused.
+ /// Only applies when is true.
+ /// Defaults to .
+ ///
+ public CursorVisibility DesiredCursorVisibility {
+ get => MultiSelect ? desiredCursorVisibility : CursorVisibility.Invisible;
+ set {
+ if (desiredCursorVisibility != value) {
+ desiredCursorVisibility = value;
+ if (HasFocus) {
+ Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
}
}
}
+ }
- ///
- /// Adds many new root level objects. Objects that are already root objects are ignored.
- ///
- /// Objects to add as new root level objects..\
- public void AddObjects (IEnumerable collection)
- {
- bool objectsAdded = false;
+ ///
+ /// Gets the that searches the collection as
+ /// the user types.
+ ///
+ public CollectionNavigator KeystrokeNavigator { get; } = new ();
- foreach (var o in collection) {
- if (!roots.ContainsKey (o)) {
- roots.Add (o, new Branch (this, null, o));
- objectsAdded = true;
- }
- }
+ ///
+ /// Contains options for changing how the tree is rendered.
+ ///
+ public TreeStyle Style { get; set; } = new ();
- if (objectsAdded) {
- InvalidateLineMap ();
- SetNeedsDisplay ();
- }
- }
+ ///
+ /// Removes all objects from the tree and clears .
+ ///
+ public void ClearObjects ()
+ {
+ SelectedObject = default;
+ multiSelectedRegions.Clear ();
+ roots = new Dictionary> ();
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
- ///
- /// Refreshes the state of the object in the tree. This will
- /// recompute children, string representation etc.
- ///
- /// This has no effect if the object is not exposed in the tree.
- ///
- /// True to also refresh all ancestors of the objects branch
- /// (starting with the root). False to refresh only the passed node.
- public void RefreshObject (T o, bool startAtTop = false)
- {
- var branch = ObjectToBranch (o);
- if (branch != null) {
- branch.Refresh (startAtTop);
- InvalidateLineMap ();
- SetNeedsDisplay ();
- }
+ ///
+ /// This event is raised when an object is activated e.g. by double clicking or
+ /// pressing .
+ ///
+ public event EventHandler> ObjectActivated;
+
+ ///
+ /// Called when the changes.
+ ///
+ public event EventHandler> SelectionChanged;
+
+ ///
+ /// Called once for each visible row during rendering. Can be used
+ /// to make last minute changes to color or text rendered
+ ///
+ public event EventHandler> DrawLine;
+
+ ///
+ public override bool OnEnter (View view)
+ {
+ Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
+ if (SelectedObject == null && Objects.Any ()) {
+ SelectedObject = Objects.First ();
}
- ///
- /// Rebuilds the tree structure for all exposed objects starting with the root objects.
- /// Call this method when you know there are changes to the tree but don't know which
- /// objects have changed (otherwise use ).
- ///
- public void RebuildTree ()
- {
- foreach (var branch in roots.Values) {
- branch.Rebuild ();
- }
+ return base.OnEnter (view);
+ }
+ ///
+ /// Adds a new root level object unless it is already a root of the tree.
+ ///
+ ///
+ public void AddObject (T o)
+ {
+ if (!roots.ContainsKey (o)) {
+ roots.Add (o, new Branch (this, null, o));
InvalidateLineMap ();
SetNeedsDisplay ();
}
+ }
- ///
- /// Returns the currently expanded children of the passed object. Returns an empty
- /// collection if the branch is not exposed or not expanded.
- ///
- /// An object in the tree.
- ///
- public IEnumerable GetChildren (T o)
- {
- var branch = ObjectToBranch (o);
+ ///
+ /// Removes the given root object from the tree
+ ///
+ ///
+ /// If is the currently then the
+ /// selection is cleared
+ ///
+ /// .
+ ///
+ public void Remove (T o)
+ {
+ if (roots.ContainsKey (o)) {
+ roots.Remove (o);
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
- if (branch == null || !branch.IsExpanded) {
- return new T [0];
+ if (Equals (SelectedObject, o)) {
+ SelectedObject = default;
}
-
- return branch.ChildBranches?.Values?.Select (b => b.Model)?.ToArray () ?? new T [0];
- }
- ///
- /// Returns the parent object of in the tree. Returns null if
- /// the object is not exposed in the tree.
- ///
- /// An object in the tree.
- ///
- public T GetParent (T o)
- {
- return ObjectToBranch (o)?.Parent?.Model;
}
+ }
- ///
- public override void OnDrawContent (Rect contentArea)
- {
- if (roots == null) {
- return;
- }
+ ///
+ /// Adds many new root level objects. Objects that are already root objects are ignored.
+ ///
+ /// Objects to add as new root level objects.
+ /// .\
+ public void AddObjects (IEnumerable collection)
+ {
+ var objectsAdded = false;
- if (TreeBuilder == null) {
- Move (0, 0);
- Driver.AddStr (NoBuilderError);
- return;
+ foreach (var o in collection) {
+ if (!roots.ContainsKey (o)) {
+ roots.Add (o, new Branch (this, null, o));
+ objectsAdded = true;
}
+ }
- var map = BuildLineMap ();
-
- for (int line = 0; line < Bounds.Height; line++) {
+ if (objectsAdded) {
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
+ }
- var idxToRender = ScrollOffsetVertical + line;
+ ///
+ /// Refreshes the state of the object in the tree. This will
+ /// recompute children, string representation etc.
+ ///
+ /// This has no effect if the object is not exposed in the tree.
+ ///
+ ///
+ /// True to also refresh all ancestors of the objects branch
+ /// (starting with the root). False to refresh only the passed node.
+ ///
+ public void RefreshObject (T o, bool startAtTop = false)
+ {
+ var branch = ObjectToBranch (o);
+ if (branch != null) {
+ branch.Refresh (startAtTop);
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
- // Is there part of the tree view to render?
- if (idxToRender < map.Count) {
- // Render the line
- map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Bounds.Width);
- } else {
+ }
- // Else clear the line to prevent stale symbols due to scrolling etc
- Move (0, line);
- Driver.SetAttribute (GetNormalColor ());
- Driver.AddStr (new string (' ', Bounds.Width));
- }
- }
+ ///
+ /// Rebuilds the tree structure for all exposed objects starting with the root objects.
+ /// Call this method when you know there are changes to the tree but don't know which
+ /// objects have changed (otherwise use ).
+ ///
+ public void RebuildTree ()
+ {
+ foreach (var branch in roots.Values) {
+ branch.Rebuild ();
}
- ///
- /// Returns the index of the object if it is currently exposed (it's
- /// parent(s) have been expanded). This can be used with
- /// and to scroll to a specific object.
- ///
- /// Uses the Equals method and returns the first index at which the object is found
- /// or -1 if it is not found.
- /// An object that appears in your tree and is currently exposed.
- /// The index the object was found at or -1 if it is not currently revealed or
- /// not in the tree at all.
- public int GetScrollOffsetOf (T o)
- {
- var map = BuildLineMap ();
- for (int i = 0; i < map.Count; i++) {
- if (map.ElementAt (i).Model.Equals (o)) {
- return i;
- }
- }
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
+
+ ///
+ /// Returns the currently expanded children of the passed object. Returns an empty
+ /// collection if the branch is not exposed or not expanded.
+ ///
+ /// An object in the tree.
+ ///
+ public IEnumerable GetChildren (T o)
+ {
+ var branch = ObjectToBranch (o);
- //object not found
- return -1;
+ if (branch == null || !branch.IsExpanded) {
+ return new T [0];
}
- ///
- /// Returns the maximum width line in the tree including prefix and expansion symbols.
- ///
- /// True to consider only rows currently visible (based on window
- /// bounds and . False to calculate the width of
- /// every exposed branch in the tree.
- ///
- public int GetContentWidth (bool visible)
- {
- var map = BuildLineMap ();
+ return branch.ChildBranches?.Values?.Select (b => b.Model)?.ToArray () ?? new T [0];
+ }
- if (map.Count == 0) {
- return 0;
- }
+ ///
+ /// Returns the parent object of in the tree. Returns null if
+ /// the object is not exposed in the tree.
+ ///
+ /// An object in the tree.
+ ///
+ public T GetParent (T o) => ObjectToBranch (o)?.Parent?.Model;
- if (visible) {
+ ///
+ public override void OnDrawContent (Rect contentArea)
+ {
+ if (roots == null) {
+ return;
+ }
- //Somehow we managed to scroll off the end of the control
- if (ScrollOffsetVertical >= map.Count) {
- return 0;
- }
+ if (TreeBuilder == null) {
+ Move (0, 0);
+ Driver.AddStr (NoBuilderError);
+ return;
+ }
- // If control has no height to it then there is no visible area for content
- if (Bounds.Height == 0) {
- return 0;
- }
+ var map = BuildLineMap ();
- return map.Skip (ScrollOffsetVertical).Take (Bounds.Height).Max (b => b.GetWidth (Driver));
+ for (var line = 0; line < Bounds.Height; line++) {
+
+ var idxToRender = ScrollOffsetVertical + line;
+
+ // Is there part of the tree view to render?
+ if (idxToRender < map.Count) {
+ // Render the line
+ map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Bounds.Width);
} else {
- return map.Max (b => b.GetWidth (Driver));
+ // Else clear the line to prevent stale symbols due to scrolling etc
+ Move (0, line);
+ Driver.SetAttribute (GetNormalColor ());
+ Driver.AddStr (new string (' ', Bounds.Width));
}
}
+ }
- ///
- /// Calculates all currently visible/expanded branches (including leafs) and outputs them
- /// by index from the top of the screen.
- ///
- /// Index 0 of the returned array is the first item that should be visible in the
- /// top of the control, index 1 is the next etc.
- ///
- internal IReadOnlyCollection> BuildLineMap ()
- {
- if (cachedLineMap != null) {
- return cachedLineMap;
- }
+ ///
+ /// Returns the index of the object if it is currently exposed (it's
+ /// parent(s) have been expanded). This can be used with
+ /// and to scroll to a specific object.
+ ///
+ ///
+ /// Uses the Equals method and returns the first index at which the object is found
+ /// or -1 if it is not found.
+ ///
+ /// An object that appears in your tree and is currently exposed.
+ ///
+ /// The index the object was found at or -1 if it is not currently revealed or
+ /// not in the tree at all.
+ ///
+ public int GetScrollOffsetOf (T o)
+ {
+ var map = BuildLineMap ();
+ for (var i = 0; i < map.Count; i++) {
+ if (map.ElementAt (i).Model.Equals (o)) {
+ return i;
+ }
+ }
+
+ //object not found
+ return -1;
+ }
+
+ ///
+ /// Returns the maximum width line in the tree including prefix and expansion symbols.
+ ///
+ ///
+ /// True to consider only rows currently visible (based on window
+ /// bounds and . False to calculate the width of
+ /// every exposed branch in the tree.
+ ///
+ ///
+ public int GetContentWidth (bool visible)
+ {
+ var map = BuildLineMap ();
- List> toReturn = new List> ();
+ if (map.Count == 0) {
+ return 0;
+ }
- foreach (var root in roots.Values) {
+ if (visible) {
- var toAdd = AddToLineMap (root, false, out var isMatch);
- if (isMatch) {
- toReturn.AddRange (toAdd);
- }
+ //Somehow we managed to scroll off the end of the control
+ if (ScrollOffsetVertical >= map.Count) {
+ return 0;
}
- cachedLineMap = new ReadOnlyCollection> (toReturn);
+ // If control has no height to it then there is no visible area for content
+ if (Bounds.Height == 0) {
+ return 0;
+ }
- // Update the collection used for search-typing
- KeystrokeNavigator.Collection = cachedLineMap.Select (b => AspectGetter (b.Model)).ToArray ();
- return cachedLineMap;
+ return map.Skip (ScrollOffsetVertical).Take (Bounds.Height).Max (b => b.GetWidth (Driver));
}
+ return map.Max (b => b.GetWidth (Driver));
+ }
- private bool IsFilterMatch (Branch branch)
- {
- return Filter?.IsMatch (branch.Model) ?? true;
+ ///
+ /// Calculates all currently visible/expanded branches (including leafs) and outputs them
+ /// by index from the top of the screen.
+ ///
+ ///
+ /// Index 0 of the returned array is the first item that should be visible in the
+ /// top of the control, index 1 is the next etc.
+ ///
+ ///
+ internal IReadOnlyCollection> BuildLineMap ()
+ {
+ if (cachedLineMap != null) {
+ return cachedLineMap;
}
- private IEnumerable> AddToLineMap (Branch currentBranch, bool parentMatches, out bool match)
- {
- bool weMatch = IsFilterMatch (currentBranch);
- bool anyChildMatches = false;
+ var toReturn = new List> ();
- var toReturn = new List> ();
- var children = new List> ();
+ foreach (var root in roots.Values) {
- if (currentBranch.IsExpanded) {
- foreach (var subBranch in currentBranch.ChildBranches.Values) {
+ var toAdd = AddToLineMap (root, false, out var isMatch);
+ if (isMatch) {
+ toReturn.AddRange (toAdd);
+ }
+ }
- foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) {
+ cachedLineMap = new ReadOnlyCollection> (toReturn);
- if (childMatch) {
- children.Add (sub);
- anyChildMatches = true;
- }
- }
- }
- }
+ // Update the collection used for search-typing
+ KeystrokeNavigator.Collection = cachedLineMap.Select (b => AspectGetter (b.Model)).ToArray ();
+ return cachedLineMap;
+ }
- if (parentMatches || weMatch || anyChildMatches) {
- match = true;
- toReturn.Add (currentBranch);
- } else {
- match = false;
- }
+ bool IsFilterMatch (Branch branch) => Filter?.IsMatch (branch.Model) ?? true;
- toReturn.AddRange (children);
- return toReturn;
- }
+ IEnumerable> AddToLineMap (Branch currentBranch, bool parentMatches, out bool match)
+ {
+ var weMatch = IsFilterMatch (currentBranch);
+ var anyChildMatches = false;
- ///
- /// Gets the that searches the collection as
- /// the user types.
- ///
- public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
+ var toReturn = new List> ();
+ var children = new List> ();
- ///
- public override bool OnProcessKeyDown (Key keyEvent)
- {
- if (!Enabled) {
- return false;
- }
+ if (currentBranch.IsExpanded) {
+ foreach (var subBranch in currentBranch.ChildBranches.Values) {
+
+ foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) {
- try {
- // BUGBUG: this should move to OnInvokingKeyBindings
- // If not a keybinding, is the key a searchable key press?
- if (CollectionNavigator.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) {
- IReadOnlyCollection> map;
-
- // If there has been a call to InvalidateMap since the last time
- // we need a new one to reflect the new exposed tree state
- map = BuildLineMap ();
-
- // Find the current selected object within the tree
- var current = map.IndexOf (b => b.Model == SelectedObject);
- var newIndex = KeystrokeNavigator?.GetNextMatchingItem (current, (char)keyEvent);
-
- if (newIndex is int && newIndex != -1) {
- SelectedObject = map.ElementAt ((int)newIndex).Model;
- EnsureVisible (selectedObject);
- SetNeedsDisplay ();
- return true;
+ if (childMatch) {
+ children.Add (sub);
+ anyChildMatches = true;
}
}
- } finally {
- if (IsInitialized) {
- PositionCursor ();
- }
}
+ }
+
+ if (parentMatches || weMatch || anyChildMatches) {
+ match = true;
+ toReturn.Add (currentBranch);
+ } else {
+ match = false;
+ }
+ toReturn.AddRange (children);
+ return toReturn;
+ }
+
+ ///
+ public override bool OnProcessKeyDown (Key keyEvent)
+ {
+ if (!Enabled) {
return false;
}
- ///
- /// Triggers the event with the .
- ///
- /// This method also ensures that the selected object is visible.
- ///
- public void ActivateSelectedObjectIfAny ()
- {
- var o = SelectedObject;
+ try {
+ // BUGBUG: this should move to OnInvokingKeyBindings
+ // If not a keybinding, is the key a searchable key press?
+ if (CollectionNavigatorBase.IsCompatibleKey (keyEvent) && AllowLetterBasedNavigation) {
+ IReadOnlyCollection> map;
+
+ // If there has been a call to InvalidateMap since the last time
+ // we need a new one to reflect the new exposed tree state
+ map = BuildLineMap ();
+
+ // Find the current selected object within the tree
+ var current = map.IndexOf (b => b.Model == SelectedObject);
+ var newIndex = KeystrokeNavigator?.GetNextMatchingItem (current, (char)keyEvent);
- if (o != null) {
- OnObjectActivated (new ObjectActivatedEventArgs (this, o));
+ if (newIndex is int && newIndex != -1) {
+ SelectedObject = map.ElementAt ((int)newIndex).Model;
+ EnsureVisible (selectedObject);
+ SetNeedsDisplay ();
+ return true;
+ }
+ }
+ } finally {
+ if (IsInitialized) {
PositionCursor ();
}
}
- ///
- ///
- /// Returns the Y coordinate within the of the
- /// tree at which would be displayed or null if
- /// it is not currently exposed (e.g. its parent is collapsed).
- ///
- ///
- /// Note that the returned value can be negative if the TreeView is scrolled
- /// down and the object is off the top of the view.
- ///
- ///
- ///
- ///
- public int? GetObjectRow (T toFind)
- {
- var idx = BuildLineMap ().IndexOf (o => o.Model.Equals (toFind));
-
- if (idx == -1)
- return null;
-
- return idx - ScrollOffsetVertical;
- }
-
- ///
- /// Moves the to the next item that begins with .
- /// This method will loop back to the start of the tree if reaching the end without finding a match.
- ///
- /// The first character of the next item you want selected.
- /// Case sensitivity of the search.
- public void AdjustSelectionToNextItemBeginningWith (char character, StringComparison caseSensitivity = StringComparison.CurrentCultureIgnoreCase)
- {
- // search for next branch that begins with that letter
- var characterAsStr = character.ToString ();
- AdjustSelectionToNext (b => AspectGetter (b.Model).StartsWith (characterAsStr, caseSensitivity));
+ return false;
+ }
+
+ ///
+ /// Triggers the event with the .
+ ///
+ /// This method also ensures that the selected object is visible.
+ ///
+ public void ActivateSelectedObjectIfAny ()
+ {
+ var o = SelectedObject;
+ if (o != null) {
+ OnObjectActivated (new ObjectActivatedEventArgs (this, o));
PositionCursor ();
}
+ }
- ///
- /// Moves the selection up by the height of the control (1 page).
- ///
- /// True if the navigation should add the covered nodes to the selected current selection.
- ///
- public void MovePageUp (bool expandSelection = false)
- {
- AdjustSelection (-Bounds.Height, expandSelection);
- }
-
- ///
- /// Moves the selection down by the height of the control (1 page).
- ///
- /// True if the navigation should add the covered nodes to the selected current selection.
- ///
- public void MovePageDown (bool expandSelection = false)
- {
- AdjustSelection (Bounds.Height, expandSelection);
- }
-
- ///
- /// Scrolls the view area down a single line without changing the current selection.
- ///
- public void ScrollDown ()
- {
- if (ScrollOffsetVertical <= ContentHeight - 2) {
- ScrollOffsetVertical++;
- SetNeedsDisplay ();
- }
+ ///
+ ///
+ /// Returns the Y coordinate within the of the
+ /// tree at which would be displayed or null if
+ /// it is not currently exposed (e.g. its parent is collapsed).
+ ///
+ ///
+ /// Note that the returned value can be negative if the TreeView is scrolled
+ /// down and the object is off the top of the view.
+ ///
+ ///
+ ///
+ ///
+ public int? GetObjectRow (T toFind)
+ {
+ var idx = BuildLineMap ().IndexOf (o => o.Model.Equals (toFind));
+
+ if (idx == -1) {
+ return null;
}
- ///
- /// Scrolls the view area up a single line without changing the current selection.
- ///
- public void ScrollUp ()
- {
- if (scrollOffsetVertical > 0) {
- ScrollOffsetVertical--;
- SetNeedsDisplay ();
- }
+ return idx - ScrollOffsetVertical;
+ }
+
+ ///
+ /// Moves the to the next item that begins with .
+ /// This method will loop back to the start of the tree if reaching the end without finding a match.
+ ///
+ /// The first character of the next item you want selected.
+ /// Case sensitivity of the search.
+ public void AdjustSelectionToNextItemBeginningWith (char character, StringComparison caseSensitivity = StringComparison.CurrentCultureIgnoreCase)
+ {
+ // search for next branch that begins with that letter
+ var characterAsStr = character.ToString ();
+ AdjustSelectionToNext (b => AspectGetter (b.Model).StartsWith (characterAsStr, caseSensitivity));
+
+ PositionCursor ();
+ }
+
+ ///
+ /// Moves the selection up by the height of the control (1 page).
+ ///
+ /// True if the navigation should add the covered nodes to the selected current selection.
+ ///
+ public void MovePageUp (bool expandSelection = false) => AdjustSelection (-Bounds.Height, expandSelection);
+
+ ///
+ /// Moves the selection down by the height of the control (1 page).
+ ///
+ /// True if the navigation should add the covered nodes to the selected current selection.
+ ///
+ public void MovePageDown (bool expandSelection = false) => AdjustSelection (Bounds.Height, expandSelection);
+
+ ///
+ /// Scrolls the view area down a single line without changing the current selection.
+ ///
+ public void ScrollDown ()
+ {
+ if (ScrollOffsetVertical <= ContentHeight - 2) {
+ ScrollOffsetVertical++;
+ SetNeedsDisplay ();
}
+ }
- ///
- /// Raises the event.
- ///
- ///
- protected virtual void OnObjectActivated (ObjectActivatedEventArgs e)
- {
- ObjectActivated?.Invoke (this, e);
- }
-
- ///
- /// Returns the object in the tree list that is currently visible.
- /// at the provided row. Returns null if no object is at that location.
- ///
- ///
- /// If you have screen coordinates then use
- /// to translate these into the client area of the .
- ///
- /// The row of the of the .
- /// The object currently displayed on this row or null.
- public T GetObjectOnRow (int row)
- {
- return HitTest (row)?.Model;
- }
-
- ///
- public override bool MouseEvent (MouseEvent me)
- {
- // If it is not an event we care about
- if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
- !me.Flags.HasFlag (ObjectActivationButton ?? MouseFlags.Button1DoubleClicked) &&
- !me.Flags.HasFlag (MouseFlags.WheeledDown) &&
- !me.Flags.HasFlag (MouseFlags.WheeledUp) &&
- !me.Flags.HasFlag (MouseFlags.WheeledRight) &&
- !me.Flags.HasFlag (MouseFlags.WheeledLeft)) {
-
- // do nothing
- return false;
- }
+ ///
+ /// Scrolls the view area up a single line without changing the current selection.
+ ///
+ public void ScrollUp ()
+ {
+ if (scrollOffsetVertical > 0) {
+ ScrollOffsetVertical--;
+ SetNeedsDisplay ();
+ }
+ }
- if (!HasFocus && CanFocus) {
- SetFocus ();
- }
+ ///
+ /// Raises the event.
+ ///
+ ///
+ protected virtual void OnObjectActivated (ObjectActivatedEventArgs e) => ObjectActivated?.Invoke (this, e);
- if (me.Flags == MouseFlags.WheeledDown) {
+ ///
+ /// Returns the object in the tree list that is currently visible.
+ /// at the provided row. Returns null if no object is at that location.
+ ///
+ ///
+ /// If you have screen coordinates then use
+ /// to translate these into the client area of the .
+ ///
+ /// The row of the of the .
+ /// The object currently displayed on this row or null.
+ public T GetObjectOnRow (int row) => HitTest (row)?.Model;
+
+ ///
+ public override bool MouseEvent (MouseEvent me)
+ {
+ // If it is not an event we care about
+ if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) &&
+ !me.Flags.HasFlag (ObjectActivationButton ?? MouseFlags.Button1DoubleClicked) &&
+ !me.Flags.HasFlag (MouseFlags.WheeledDown) &&
+ !me.Flags.HasFlag (MouseFlags.WheeledUp) &&
+ !me.Flags.HasFlag (MouseFlags.WheeledRight) &&
+ !me.Flags.HasFlag (MouseFlags.WheeledLeft)) {
+
+ // do nothing
+ return false;
+ }
- ScrollDown ();
+ if (!HasFocus && CanFocus) {
+ SetFocus ();
+ }
- return true;
- } else if (me.Flags == MouseFlags.WheeledUp) {
- ScrollUp ();
+ if (me.Flags == MouseFlags.WheeledDown) {
- return true;
- }
+ ScrollDown ();
- if (me.Flags == MouseFlags.WheeledRight) {
+ return true;
+ }
+ if (me.Flags == MouseFlags.WheeledUp) {
+ ScrollUp ();
- ScrollOffsetHorizontal++;
- SetNeedsDisplay ();
+ return true;
+ }
- return true;
- } else if (me.Flags == MouseFlags.WheeledLeft) {
- ScrollOffsetHorizontal--;
- SetNeedsDisplay ();
+ if (me.Flags == MouseFlags.WheeledRight) {
- return true;
- }
+ ScrollOffsetHorizontal++;
+ SetNeedsDisplay ();
+
+ return true;
+ }
+ if (me.Flags == MouseFlags.WheeledLeft) {
+ ScrollOffsetHorizontal--;
+ SetNeedsDisplay ();
- if (me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+ return true;
+ }
- // The line they clicked on a branch
- var clickedBranch = HitTest (me.Y);
+ if (me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
- if (clickedBranch == null) {
- return false;
- }
+ // The line they clicked on a branch
+ var clickedBranch = HitTest (me.Y);
- bool isExpandToggleAttempt = clickedBranch.IsHitOnExpandableSymbol (Driver, me.X);
+ if (clickedBranch == null) {
+ return false;
+ }
- // If we are already selected (double click)
- if (Equals (SelectedObject, clickedBranch.Model)) {
- isExpandToggleAttempt = true;
- }
+ var isExpandToggleAttempt = clickedBranch.IsHitOnExpandableSymbol (Driver, me.X);
- // if they clicked on the +/- expansion symbol
- if (isExpandToggleAttempt) {
+ // If we are already selected (double click)
+ if (Equals (SelectedObject, clickedBranch.Model)) {
+ isExpandToggleAttempt = true;
+ }
- if (clickedBranch.IsExpanded) {
- clickedBranch.Collapse ();
- InvalidateLineMap ();
- } else
- if (clickedBranch.CanExpand ()) {
- clickedBranch.Expand ();
- InvalidateLineMap ();
- } else {
- SelectedObject = clickedBranch.Model; // It is a leaf node
- multiSelectedRegions.Clear ();
- }
+ // if they clicked on the +/- expansion symbol
+ if (isExpandToggleAttempt) {
+
+ if (clickedBranch.IsExpanded) {
+ clickedBranch.Collapse ();
+ InvalidateLineMap ();
+ } else if (clickedBranch.CanExpand ()) {
+ clickedBranch.Expand ();
+ InvalidateLineMap ();
} else {
- // It is a first click somewhere in the current line that doesn't look like an expansion/collapse attempt
- SelectedObject = clickedBranch.Model;
+ SelectedObject = clickedBranch.Model; // It is a leaf node
multiSelectedRegions.Clear ();
}
-
- SetNeedsDisplay ();
- return true;
+ } else {
+ // It is a first click somewhere in the current line that doesn't look like an expansion/collapse attempt
+ SelectedObject = clickedBranch.Model;
+ multiSelectedRegions.Clear ();
}
- // If it is activation via mouse (e.g. double click)
- if (ObjectActivationButton.HasValue && me.Flags.HasFlag (ObjectActivationButton.Value)) {
- // The line they clicked on a branch
- var clickedBranch = HitTest (me.Y);
+ SetNeedsDisplay ();
+ return true;
+ }
+
+ // If it is activation via mouse (e.g. double click)
+ if (ObjectActivationButton.HasValue && me.Flags.HasFlag (ObjectActivationButton.Value)) {
+ // The line they clicked on a branch
+ var clickedBranch = HitTest (me.Y);
- if (clickedBranch == null) {
- return false;
- }
+ if (clickedBranch == null) {
+ return false;
+ }
- // Double click changes the selection to the clicked node as well as triggering
- // activation otherwise it feels wierd
- SelectedObject = clickedBranch.Model;
- SetNeedsDisplay ();
+ // Double click changes the selection to the clicked node as well as triggering
+ // activation otherwise it feels wierd
+ SelectedObject = clickedBranch.Model;
+ SetNeedsDisplay ();
- // trigger activation event
- OnObjectActivated (new ObjectActivatedEventArgs (this, clickedBranch.Model));
+ // trigger activation event
+ OnObjectActivated (new ObjectActivatedEventArgs (this, clickedBranch.Model));
- // mouse event is handled.
- return true;
- }
- return false;
+ // mouse event is handled.
+ return true;
}
+ return false;
+ }
- ///
- /// Returns the branch at the given client
- /// coordinate e.g. following a click event.
- ///
- /// Client Y position in the controls bounds.
- /// The clicked branch or null if outside of tree region.
- private Branch HitTest (int y)
- {
- var map = BuildLineMap ();
-
- var idx = y + ScrollOffsetVertical;
+ ///
+ /// Returns the branch at the given client
+ /// coordinate e.g. following a click event.
+ ///
+ /// Client Y position in the controls bounds.
+ /// The clicked branch or null if outside of tree region.
+ Branch HitTest (int y)
+ {
+ var map = BuildLineMap ();
- // click is outside any visible nodes
- if (idx < 0 || idx >= map.Count) {
- return null;
- }
+ var idx = y + ScrollOffsetVertical;
- // The line they clicked on
- return map.ElementAt (idx);
+ // click is outside any visible nodes
+ if (idx < 0 || idx >= map.Count) {
+ return null;
}
- ///
- /// Positions the cursor at the start of the selected objects line (if visible).
- ///
- public override void PositionCursor ()
- {
- if (CanFocus && HasFocus && Visible && SelectedObject != null) {
+ // The line they clicked on
+ return map.ElementAt (idx);
+ }
- var map = BuildLineMap ();
- var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
+ ///
+ /// Positions the cursor at the start of the selected objects line (if visible).
+ ///
+ public override void PositionCursor ()
+ {
+ if (CanFocus && HasFocus && Visible && SelectedObject != null) {
- // if currently selected line is visible
- if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Bounds.Height) {
- Move (0, idx - ScrollOffsetVertical);
- } else {
- base.PositionCursor ();
- }
+ var map = BuildLineMap ();
+ var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
+ // if currently selected line is visible
+ if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Bounds.Height) {
+ Move (0, idx - ScrollOffsetVertical);
} else {
base.PositionCursor ();
}
+
+ } else {
+ base.PositionCursor ();
}
+ }
- ///
- /// Determines systems behaviour when the left arrow key is pressed. Default behaviour is
- /// to collapse the current tree node if possible otherwise changes selection to current
- /// branches parent.
- ///
- protected virtual void CursorLeft (bool ctrl)
- {
- if (IsExpanded (SelectedObject)) {
+ ///
+ /// Determines systems behaviour when the left arrow key is pressed. Default behaviour is
+ /// to collapse the current tree node if possible otherwise changes selection to current
+ /// branches parent.
+ ///
+ protected virtual void CursorLeft (bool ctrl)
+ {
+ if (IsExpanded (SelectedObject)) {
- if (ctrl) {
- CollapseAll (SelectedObject);
- } else {
- Collapse (SelectedObject);
- }
+ if (ctrl) {
+ CollapseAll (SelectedObject);
} else {
- var parent = GetParent (SelectedObject);
+ Collapse (SelectedObject);
+ }
+ } else {
+ var parent = GetParent (SelectedObject);
- if (parent != null) {
- SelectedObject = parent;
- AdjustSelection (0);
- SetNeedsDisplay ();
- }
+ if (parent != null) {
+ SelectedObject = parent;
+ AdjustSelection (0);
+ SetNeedsDisplay ();
}
}
+ }
- ///
- /// Changes the to the first root object and resets
- /// the to 0.
- ///
- public void GoToFirst ()
- {
- ScrollOffsetVertical = 0;
- SelectedObject = roots.Keys.FirstOrDefault ();
+ ///
+ /// Changes the to the first root object and resets
+ /// the to 0.
+ ///
+ public void GoToFirst ()
+ {
+ ScrollOffsetVertical = 0;
+ SelectedObject = roots.Keys.FirstOrDefault ();
- SetNeedsDisplay ();
- }
+ SetNeedsDisplay ();
+ }
- ///
- /// Changes the to the last object in the tree and scrolls so
- /// that it is visible.
- ///
- public void GoToEnd ()
- {
- var map = BuildLineMap ();
- ScrollOffsetVertical = Math.Max (0, map.Count - Bounds.Height + 1);
- SelectedObject = map.LastOrDefault ()?.Model;
+ ///
+ /// Changes the to the last object in the tree and scrolls so
+ /// that it is visible.
+ ///
+ public void GoToEnd ()
+ {
+ var map = BuildLineMap ();
+ ScrollOffsetVertical = Math.Max (0, map.Count - Bounds.Height + 1);
+ SelectedObject = map.LastOrDefault ()?.Model;
- SetNeedsDisplay ();
+ SetNeedsDisplay ();
+ }
+
+ ///
+ /// Changes the to and scrolls to ensure
+ /// it is visible. Has no effect if is not exposed in the tree (e.g.
+ /// its parents are collapsed).
+ ///
+ ///
+ public void GoTo (T toSelect)
+ {
+ if (ObjectToBranch (toSelect) == null) {
+ return;
}
- ///
- /// Changes the to and scrolls to ensure
- /// it is visible. Has no effect if is not exposed in the tree (e.g.
- /// its parents are collapsed).
- ///
- ///
- public void GoTo (T toSelect)
- {
- if (ObjectToBranch (toSelect) == null) {
- return;
- }
+ SelectedObject = toSelect;
+ EnsureVisible (toSelect);
+ SetNeedsDisplay ();
+ }
- SelectedObject = toSelect;
- EnsureVisible (toSelect);
- SetNeedsDisplay ();
+ ///
+ /// The number of screen lines to move the currently selected object by. Supports negative values.
+ /// . Each branch occupies 1 line on screen.
+ ///
+ ///
+ /// If nothing is currently selected or the selected object is no longer in the tree
+ /// then the first object in the tree is selected instead.
+ ///
+ /// Positive to move the selection down the screen, negative to move it up
+ ///
+ /// True to expand the selection (assuming
+ /// is enabled). False to replace.
+ ///
+ public void AdjustSelection (int offset, bool expandSelection = false)
+ {
+ // if it is not a shift click or we don't allow multi select
+ if (!expandSelection || !MultiSelect) {
+ multiSelectedRegions.Clear ();
}
- ///
- /// The number of screen lines to move the currently selected object by. Supports negative values.
- /// . Each branch occupies 1 line on screen.
- ///
- /// If nothing is currently selected or the selected object is no longer in the tree
- /// then the first object in the tree is selected instead.
- /// Positive to move the selection down the screen, negative to move it up
- /// True to expand the selection (assuming
- /// is enabled). False to replace.
- public void AdjustSelection (int offset, bool expandSelection = false)
- {
- // if it is not a shift click or we don't allow multi select
- if (!expandSelection || !MultiSelect) {
- multiSelectedRegions.Clear ();
- }
+ if (SelectedObject == null) {
+ SelectedObject = roots.Keys.FirstOrDefault ();
+ } else {
+ var map = BuildLineMap ();
- if (SelectedObject == null) {
+ var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
+
+ if (idx == -1) {
+ // The current selection has disapeared!
SelectedObject = roots.Keys.FirstOrDefault ();
} else {
- var map = BuildLineMap ();
+ var newIdx = Math.Min (Math.Max (0, idx + offset), map.Count - 1);
- var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
+ var newBranch = map.ElementAt (newIdx);
- if (idx == -1) {
- // The current selection has disapeared!
- SelectedObject = roots.Keys.FirstOrDefault ();
- } else {
- var newIdx = Math.Min (Math.Max (0, idx + offset), map.Count - 1);
-
- var newBranch = map.ElementAt (newIdx);
-
- // If it is a multi selection
- if (expandSelection && MultiSelect) {
- if (multiSelectedRegions.Any ()) {
- // expand the existing head selection
- var head = multiSelectedRegions.Pop ();
- multiSelectedRegions.Push (new TreeSelection (head.Origin, newIdx, map));
- } else {
- // or start a new multi selection region
- multiSelectedRegions.Push (new TreeSelection (map.ElementAt (idx), newIdx, map));
- }
+ // If it is a multi selection
+ if (expandSelection && MultiSelect) {
+ if (multiSelectedRegions.Any ()) {
+ // expand the existing head selection
+ var head = multiSelectedRegions.Pop ();
+ multiSelectedRegions.Push (new TreeSelection (head.Origin, newIdx, map));
+ } else {
+ // or start a new multi selection region
+ multiSelectedRegions.Push (new TreeSelection (map.ElementAt (idx), newIdx, map));
}
+ }
- SelectedObject = newBranch.Model;
+ SelectedObject = newBranch.Model;
- EnsureVisible (SelectedObject);
- }
+ EnsureVisible (SelectedObject);
}
- SetNeedsDisplay ();
}
+ SetNeedsDisplay ();
+ }
- ///
- /// Moves the selection to the first child in the currently selected level.
- ///
- public void AdjustSelectionToBranchStart ()
- {
- var o = SelectedObject;
- if (o == null) {
- return;
- }
-
- var map = BuildLineMap ();
+ ///
+ /// Moves the selection to the first child in the currently selected level.
+ ///
+ public void AdjustSelectionToBranchStart ()
+ {
+ var o = SelectedObject;
+ if (o == null) {
+ return;
+ }
- int currentIdx = map.IndexOf (b => Equals (b.Model, o));
+ var map = BuildLineMap ();
- if (currentIdx == -1) {
- return;
- }
+ var currentIdx = map.IndexOf (b => Equals (b.Model, o));
- var currentBranch = map.ElementAt (currentIdx);
- var next = currentBranch;
+ if (currentIdx == -1) {
+ return;
+ }
- for (; currentIdx >= 0; currentIdx--) {
- //if it is the beginning of the current depth of branch
- if (currentBranch.Depth != next.Depth) {
+ var currentBranch = map.ElementAt (currentIdx);
+ var next = currentBranch;
- SelectedObject = currentBranch.Model;
- EnsureVisible (currentBranch.Model);
- SetNeedsDisplay ();
- return;
- }
+ for (; currentIdx >= 0; currentIdx--) {
+ //if it is the beginning of the current depth of branch
+ if (currentBranch.Depth != next.Depth) {
- // look at next branch up for consideration
- currentBranch = next;
- next = map.ElementAt (currentIdx);
+ SelectedObject = currentBranch.Model;
+ EnsureVisible (currentBranch.Model);
+ SetNeedsDisplay ();
+ return;
}
- // We ran all the way to top of tree
- GoToFirst ();
+ // look at next branch up for consideration
+ currentBranch = next;
+ next = map.ElementAt (currentIdx);
}
- ///
- /// Moves the selection to the last child in the currently selected level.
- ///
- public void AdjustSelectionToBranchEnd ()
- {
- var o = SelectedObject;
- if (o == null) {
- return;
- }
+ // We ran all the way to top of tree
+ GoToFirst ();
+ }
- var map = BuildLineMap ();
+ ///
+ /// Moves the selection to the last child in the currently selected level.
+ ///
+ public void AdjustSelectionToBranchEnd ()
+ {
+ var o = SelectedObject;
+ if (o == null) {
+ return;
+ }
- int currentIdx = map.IndexOf (b => Equals (b.Model, o));
+ var map = BuildLineMap ();
- if (currentIdx == -1) {
- return;
- }
+ var currentIdx = map.IndexOf (b => Equals (b.Model, o));
- var currentBranch = map.ElementAt (currentIdx);
- var next = currentBranch;
+ if (currentIdx == -1) {
+ return;
+ }
- for (; currentIdx < map.Count; currentIdx++) {
- //if it is the end of the current depth of branch
- if (currentBranch.Depth != next.Depth) {
+ var currentBranch = map.ElementAt (currentIdx);
+ var next = currentBranch;
- SelectedObject = currentBranch.Model;
- EnsureVisible (currentBranch.Model);
- SetNeedsDisplay ();
- return;
- }
+ for (; currentIdx < map.Count; currentIdx++) {
+ //if it is the end of the current depth of branch
+ if (currentBranch.Depth != next.Depth) {
- // look at next branch for consideration
- currentBranch = next;
- next = map.ElementAt (currentIdx);
+ SelectedObject = currentBranch.Model;
+ EnsureVisible (currentBranch.Model);
+ SetNeedsDisplay ();
+ return;
}
- GoToEnd ();
+
+ // look at next branch for consideration
+ currentBranch = next;
+ next = map.ElementAt (currentIdx);
}
+ GoToEnd ();
+ }
- ///
- /// Sets the selection to the next branch that matches the .
- ///
- ///
- private void AdjustSelectionToNext (Func, bool> predicate)
- {
- var map = BuildLineMap ();
+ ///
+ /// Sets the selection to the next branch that matches the .
+ ///
+ ///
+ void AdjustSelectionToNext (Func, bool> predicate)
+ {
+ var map = BuildLineMap ();
- // empty map means we can't select anything anyway
- if (map.Count == 0) {
- return;
- }
+ // empty map means we can't select anything anyway
+ if (map.Count == 0) {
+ return;
+ }
- // Start searching from the first element in the map
- var idxStart = 0;
+ // Start searching from the first element in the map
+ var idxStart = 0;
- // or the current selected branch
- if (SelectedObject != null) {
- idxStart = map.IndexOf (b => Equals (b.Model, SelectedObject));
- }
+ // or the current selected branch
+ if (SelectedObject != null) {
+ idxStart = map.IndexOf (b => Equals (b.Model, SelectedObject));
+ }
- // if currently selected object mysteriously vanished, search from beginning
- if (idxStart == -1) {
- idxStart = 0;
- }
+ // if currently selected object mysteriously vanished, search from beginning
+ if (idxStart == -1) {
+ idxStart = 0;
+ }
- // loop around all indexes and back to first index
- for (int idxCur = (idxStart + 1) % map.Count; idxCur != idxStart; idxCur = (idxCur + 1) % map.Count) {
- if (predicate (map.ElementAt (idxCur))) {
- SelectedObject = map.ElementAt (idxCur).Model;
- EnsureVisible (map.ElementAt (idxCur).Model);
- SetNeedsDisplay ();
- return;
- }
+ // loop around all indexes and back to first index
+ for (var idxCur = (idxStart + 1) % map.Count; idxCur != idxStart; idxCur = (idxCur + 1) % map.Count) {
+ if (predicate (map.ElementAt (idxCur))) {
+ SelectedObject = map.ElementAt (idxCur).Model;
+ EnsureVisible (map.ElementAt (idxCur).Model);
+ SetNeedsDisplay ();
+ return;
}
}
+ }
- ///
- /// Adjusts the to ensure the given
- /// is visible. Has no effect if already visible.
- ///
- public void EnsureVisible (T model)
- {
- var map = BuildLineMap ();
+ ///
+ /// Adjusts the to ensure the given
+ /// is visible. Has no effect if already visible.
+ ///
+ public void EnsureVisible (T model)
+ {
+ var map = BuildLineMap ();
- var idx = map.IndexOf (b => Equals (b.Model, model));
+ var idx = map.IndexOf (b => Equals (b.Model, model));
- if (idx == -1) {
- return;
- }
+ if (idx == -1) {
+ return;
+ }
- /*this -1 allows for possible horizontal scroll bar in the last row of the control*/
- int leaveSpace = Style.LeaveLastRow ? 1 : 0;
+ /*this -1 allows for possible horizontal scroll bar in the last row of the control*/
+ var leaveSpace = Style.LeaveLastRow ? 1 : 0;
- if (idx < ScrollOffsetVertical) {
- //if user has scrolled up too far to see their selection
- ScrollOffsetVertical = idx;
- } else if (idx >= ScrollOffsetVertical + Bounds.Height - leaveSpace) {
+ if (idx < ScrollOffsetVertical) {
+ //if user has scrolled up too far to see their selection
+ ScrollOffsetVertical = idx;
+ } else if (idx >= ScrollOffsetVertical + Bounds.Height - leaveSpace) {
- //if user has scrolled off bottom of visible tree
- ScrollOffsetVertical = Math.Max (0, (idx + 1) - (Bounds.Height - leaveSpace));
- }
+ //if user has scrolled off bottom of visible tree
+ ScrollOffsetVertical = Math.Max (0, idx + 1 - (Bounds.Height - leaveSpace));
}
+ }
+
+ ///
+ /// Expands the currently .
+ ///
+ public void Expand () => Expand (SelectedObject);
- ///
- /// Expands the currently .
- ///
- public void Expand ()
- {
- Expand (SelectedObject);
+ ///
+ /// Expands the supplied object if it is contained in the tree (either as a root object or
+ /// as an exposed branch object).
+ ///
+ /// The object to expand.
+ public void Expand (T toExpand)
+ {
+ if (toExpand == null) {
+ return;
}
- ///
- /// Expands the supplied object if it is contained in the tree (either as a root object or
- /// as an exposed branch object).
- ///
- /// The object to expand.
- public void Expand (T toExpand)
- {
- if (toExpand == null) {
- return;
- }
+ ObjectToBranch (toExpand)?.Expand ();
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
- ObjectToBranch (toExpand)?.Expand ();
- InvalidateLineMap ();
- SetNeedsDisplay ();
+ ///
+ /// Expands the supplied object and all child objects.
+ ///
+ /// The object to expand.
+ public void ExpandAll (T toExpand)
+ {
+ if (toExpand == null) {
+ return;
}
- ///
- /// Expands the supplied object and all child objects.
- ///
- /// The object to expand.
- public void ExpandAll (T toExpand)
- {
- if (toExpand == null) {
- return;
- }
+ ObjectToBranch (toExpand)?.ExpandAll ();
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
- ObjectToBranch (toExpand)?.ExpandAll ();
- InvalidateLineMap ();
- SetNeedsDisplay ();
+ ///
+ /// Fully expands all nodes in the tree, if the tree is very big and built dynamically this
+ /// may take a while (e.g. for file system).
+ ///
+ public void ExpandAll ()
+ {
+ foreach (var item in roots) {
+ item.Value.ExpandAll ();
}
- ///
- /// Fully expands all nodes in the tree, if the tree is very big and built dynamically this
- /// may take a while (e.g. for file system).
- ///
- public void ExpandAll ()
- {
- foreach (var item in roots) {
- item.Value.ExpandAll ();
- }
- InvalidateLineMap ();
- SetNeedsDisplay ();
- }
- ///
- /// Returns true if the given object is exposed in the tree and can be
- /// expanded otherwise false.
- ///
- ///
- ///
- public bool CanExpand (T o)
- {
- return ObjectToBranch (o)?.CanExpand () ?? false;
- }
-
- ///
- /// Returns true if the given object is exposed in the tree and
- /// expanded otherwise false.
- ///
- ///
- ///
- public bool IsExpanded (T o)
- {
- return ObjectToBranch (o)?.IsExpanded ?? false;
- }
-
- ///
- /// Collapses the
- ///
- public void Collapse ()
- {
- Collapse (selectedObject);
- }
-
- ///
- /// Collapses the supplied object if it is currently expanded .
- ///
- /// The object to collapse.
- public void Collapse (T toCollapse)
- {
- CollapseImpl (toCollapse, false);
- }
-
- ///
- /// Collapses the supplied object if it is currently expanded. Also collapses all children
- /// branches (this will only become apparent when/if the user expands it again).
- ///
- /// The object to collapse.
- public void CollapseAll (T toCollapse)
- {
- CollapseImpl (toCollapse, true);
- }
-
- ///
- /// Collapses all root nodes in the tree.
- ///
- public void CollapseAll ()
- {
- foreach (var item in roots) {
- item.Value.Collapse ();
- }
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
- InvalidateLineMap ();
- SetNeedsDisplay ();
- }
+ ///
+ /// Returns true if the given object is exposed in the tree and can be
+ /// expanded otherwise false.
+ ///
+ ///
+ ///
+ public bool CanExpand (T o) => ObjectToBranch (o)?.CanExpand () ?? false;
- ///
- /// Implementation of and . Performs
- /// operation and updates selection if disapeared.
- ///
- ///
- ///
- protected void CollapseImpl (T toCollapse, bool all)
- {
- if (toCollapse == null) {
- return;
- }
+ ///
+ /// Returns true if the given object is exposed in the tree and
+ /// expanded otherwise false.
+ ///
+ ///
+ ///
+ public bool IsExpanded (T o) => ObjectToBranch (o)?.IsExpanded ?? false;
- var branch = ObjectToBranch (toCollapse);
+ ///
+ /// Collapses the
+ ///
+ public void Collapse () => Collapse (selectedObject);
- // Nothing to collapse
- if (branch == null) {
- return;
- }
+ ///
+ /// Collapses the supplied object if it is currently expanded .
+ ///
+ /// The object to collapse.
+ public void Collapse (T toCollapse) => CollapseImpl (toCollapse, false);
- if (all) {
- branch.CollapseAll ();
- } else {
- branch.Collapse ();
- }
+ ///
+ /// Collapses the supplied object if it is currently expanded. Also collapses all children
+ /// branches (this will only become apparent when/if the user expands it again).
+ ///
+ /// The object to collapse.
+ public void CollapseAll (T toCollapse) => CollapseImpl (toCollapse, true);
- if (SelectedObject != null && ObjectToBranch (SelectedObject) == null) {
- // If the old selection suddenly became invalid then clear it
- SelectedObject = null;
- }
+ ///
+ /// Collapses all root nodes in the tree.
+ ///
+ public void CollapseAll ()
+ {
+ foreach (var item in roots) {
+ item.Value.Collapse ();
+ }
- InvalidateLineMap ();
- SetNeedsDisplay ();
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
+
+ ///
+ /// Implementation of and . Performs
+ /// operation and updates selection if disapeared.
+ ///
+ ///
+ ///
+ protected void CollapseImpl (T toCollapse, bool all)
+ {
+ if (toCollapse == null) {
+ return;
}
- ///
- /// Clears any cached results of the tree state.
- ///
- public void InvalidateLineMap ()
- {
- cachedLineMap = null;
- }
-
- ///
- /// Returns the corresponding in the tree for
- /// . This will not work for objects hidden
- /// by their parent being collapsed.
- ///
- ///
- /// The branch for or null if it is not currently
- /// exposed in the tree.
- private Branch ObjectToBranch (T toFind)
- {
- return BuildLineMap ().FirstOrDefault (o => o.Model.Equals (toFind));
- }
-
- ///
- /// Returns true if the is either the
- /// or part of a .
- ///
- ///
- ///
- public bool IsSelected (T model)
- {
- return Equals (SelectedObject, model) ||
- (MultiSelect && multiSelectedRegions.Any (s => s.Contains (model)));
- }
-
- ///
- /// Returns (if not null) and all multi selected objects if
- /// is true
- ///
- ///
- public IEnumerable GetAllSelectedObjects ()
- {
- var map = BuildLineMap ();
+ var branch = ObjectToBranch (toCollapse);
- // To determine multi selected objects, start with the line map, that avoids yielding
- // hidden nodes that were selected then the parent collapsed e.g. programmatically or
- // with mouse click
- if (MultiSelect) {
- foreach (var m in map.Select (b => b.Model).Where (IsSelected)) {
- yield return m;
- }
- } else {
- if (SelectedObject != null) {
- yield return SelectedObject;
- }
- }
+ // Nothing to collapse
+ if (branch == null) {
+ return;
}
- ///
- /// Selects all objects in the tree when is enabled otherwise
- /// does nothing.
- ///
- public void SelectAll ()
- {
- if (!MultiSelect) {
- return;
- }
+ if (all) {
+ branch.CollapseAll ();
+ } else {
+ branch.Collapse ();
+ }
- multiSelectedRegions.Clear ();
+ if (SelectedObject != null && ObjectToBranch (SelectedObject) == null) {
+ // If the old selection suddenly became invalid then clear it
+ SelectedObject = null;
+ }
- var map = BuildLineMap ();
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
- if (map.Count == 0) {
- return;
- }
+ ///
+ /// Clears any cached results of the tree state.
+ ///
+ public void InvalidateLineMap () => cachedLineMap = null;
- multiSelectedRegions.Push (new TreeSelection (map.ElementAt (0), map.Count, map));
- SetNeedsDisplay ();
+ ///
+ /// Returns the corresponding in the tree for
+ /// . This will not work for objects hidden
+ /// by their parent being collapsed.
+ ///
+ ///
+ ///
+ /// The branch for or null if it is not currently
+ /// exposed in the tree.
+ ///
+ Branch ObjectToBranch (T toFind) => BuildLineMap ().FirstOrDefault (o => o.Model.Equals (toFind));
- OnSelectionChanged (new SelectionChangedEventArgs (this, SelectedObject, SelectedObject));
- }
+ ///
+ /// Returns true if the is either the
+ /// or part of a .
+ ///
+ ///
+ ///
+ public bool IsSelected (T model) => Equals (SelectedObject, model) ||
+ MultiSelect && multiSelectedRegions.Any (s => s.Contains (model));
- ///
- /// Raises the SelectionChanged event.
- ///
- ///
- protected virtual void OnSelectionChanged (SelectionChangedEventArgs e)
- {
- SelectionChanged?.Invoke (this, e);
+ ///
+ /// Returns (if not null) and all multi selected objects if
+ /// is true
+ ///
+ ///
+ public IEnumerable GetAllSelectedObjects ()
+ {
+ var map = BuildLineMap ();
+
+ // To determine multi selected objects, start with the line map, that avoids yielding
+ // hidden nodes that were selected then the parent collapsed e.g. programmatically or
+ // with mouse click
+ if (MultiSelect) {
+ foreach (var m in map.Select (b => b.Model).Where (IsSelected)) {
+ yield return m;
+ }
+ } else {
+ if (SelectedObject != null) {
+ yield return SelectedObject;
+ }
}
+ }
- ///
- /// Raises the DrawLine event
- ///
- ///
- internal void OnDrawLine (DrawTreeViewLineEventArgs e)
- {
- DrawLine?.Invoke (this, e);
+ ///
+ /// Selects all objects in the tree when is enabled otherwise
+ /// does nothing.
+ ///
+ public void SelectAll ()
+ {
+ if (!MultiSelect) {
+ return;
}
- ///
- protected override void Dispose (bool disposing)
- {
- base.Dispose (disposing);
+ multiSelectedRegions.Clear ();
- ColorGetter = null;
+ var map = BuildLineMap ();
+
+ if (map.Count == 0) {
+ return;
}
+
+ multiSelectedRegions.Push (new TreeSelection (map.ElementAt (0), map.Count, map));
+ SetNeedsDisplay ();
+
+ OnSelectionChanged (new SelectionChangedEventArgs (this, SelectedObject, SelectedObject));
}
- class TreeSelection where T : class {
- public Branch Origin { get; }
+ ///
+ /// Raises the SelectionChanged event.
+ ///
+ ///
+ protected virtual void OnSelectionChanged (SelectionChangedEventArgs e) => SelectionChanged?.Invoke (this, e);
- private HashSet included = new HashSet ();
+ ///
+ /// Raises the DrawLine event
+ ///
+ ///
+ internal void OnDrawLine (DrawTreeViewLineEventArgs e) => DrawLine?.Invoke (this, e);
- ///
- /// Creates a new selection between two branches in the tree
- ///
- ///
- ///
- ///
- public TreeSelection (Branch from, int toIndex, IReadOnlyCollection> map)
- {
- Origin = from;
- included.Add (Origin.Model);
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ base.Dispose (disposing);
- var oldIdx = map.IndexOf (from);
+ ColorGetter = null;
+ }
+}
- var lowIndex = Math.Min (oldIdx, toIndex);
- var highIndex = Math.Max (oldIdx, toIndex);
+class TreeSelection where T : class {
- // Select everything between the old and new indexes
- foreach (var alsoInclude in map.Skip (lowIndex).Take (highIndex - lowIndex)) {
- included.Add (alsoInclude.Model);
- }
+ readonly HashSet included = new ();
+ ///
+ /// Creates a new selection between two branches in the tree
+ ///
+ ///
+ ///
+ ///
+ public TreeSelection (Branch from, int toIndex, IReadOnlyCollection> map)
+ {
+ Origin = from;
+ included.Add (Origin.Model);
+
+ var oldIdx = map.IndexOf (from);
+
+ var lowIndex = Math.Min (oldIdx, toIndex);
+ var highIndex = Math.Max (oldIdx, toIndex);
+
+ // Select everything between the old and new indexes
+ foreach (var alsoInclude in map.Skip (lowIndex).Take (highIndex - lowIndex)) {
+ included.Add (alsoInclude.Model);
}
- public bool Contains (T model)
- {
- return included.Contains (model);
- }
+
}
+
+ public Branch Origin { get; }
+
+ public bool Contains (T model) => included.Contains (model);
}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs
index 91600a8cd7..62545fa382 100644
--- a/UICatalog/Scenarios/TreeViewFileSystem.cs
+++ b/UICatalog/Scenarios/TreeViewFileSystem.cs
@@ -364,20 +364,21 @@ void SetMultiSelect ()
void SetCustomColors ()
{
- var hidden = new ColorScheme {
- Focus = new Attribute (Color.BrightRed, _treeViewFiles.ColorScheme.Focus.Background),
- Normal = new Attribute (Color.BrightYellow, _treeViewFiles.ColorScheme.Normal.Background)
- };
-
_miCustomColors.Checked = !_miCustomColors.Checked;
if (_miCustomColors.Checked == true) {
_treeViewFiles.ColorGetter = (m) => {
if (m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) {
- return hidden;
+ return new ColorScheme {
+ Focus = new Attribute (Color.BrightRed, _treeViewFiles.ColorScheme.Focus.Background),
+ Normal = new Attribute (Color.BrightYellow, _treeViewFiles.ColorScheme.Normal.Background)
+ }; ;
}
if (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) {
- return hidden;
+ return new ColorScheme {
+ Focus = new Attribute (Color.BrightRed, _treeViewFiles.ColorScheme.Focus.Background),
+ Normal = new Attribute (Color.BrightYellow, _treeViewFiles.ColorScheme.Normal.Background)
+ }; ;
}
return null;
};
diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs
index 5dbc3a9c09..00c9ae1722 100644
--- a/UnitTests/Application/ApplicationTests.cs
+++ b/UnitTests/Application/ApplicationTests.cs
@@ -566,7 +566,7 @@ public void Shutdown_Resets_SyncContext ()
}
#endregion
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Begin_Sets_Application_Top_To_Console_Size ()
{
Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame);
diff --git a/UnitTests/Configuration/ConfigurationMangerTests.cs b/UnitTests/Configuration/ConfigurationMangerTests.cs
index 5c9ed616e6..a2ec1e6a95 100644
--- a/UnitTests/Configuration/ConfigurationMangerTests.cs
+++ b/UnitTests/Configuration/ConfigurationMangerTests.cs
@@ -297,7 +297,7 @@ public void TestConfigPropertyOmitClassName ()
Assert.Equal (pi, Themes ["Default"] ["ColorSchemes"].PropertyInfo);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void TestConfigurationManagerToJson ()
{
Reset ();
@@ -518,7 +518,7 @@ public void TestConfigurationManagerUpdateFromJson ()
Assert.Equal (new Color (Color.Blue), Colors.ColorSchemes ["Base"].Normal.Background);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void TestConfigurationManagerInvalidJsonThrows ()
{
ThrowOnJsonErrors = true;
@@ -690,7 +690,7 @@ public void TestConfigurationManagerInvalidJsonLogs ()
ThrowOnJsonErrors = false;
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void LoadConfigurationFromAllSources_ShouldLoadSettingsFromAllSources ()
{
//var _configFilename = "config.json";
diff --git a/UnitTests/Configuration/JsonConverterTests.cs b/UnitTests/Configuration/JsonConverterTests.cs
index bc3f46924c..8fb9423f22 100644
--- a/UnitTests/Configuration/JsonConverterTests.cs
+++ b/UnitTests/Configuration/JsonConverterTests.cs
@@ -191,7 +191,7 @@ public void TestDeserialize ()
Assert.Equal (Color.BrightGreen, attribute.Background.ColorName);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void TestSerialize ()
{
// Test serializing to human-readable color names
@@ -229,7 +229,7 @@ public class ColorSchemeJsonConverterTests {
// }
// }
// }";
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void TestColorSchemesSerialization ()
{
// Arrange
diff --git a/UnitTests/Configuration/SettingsScopeTests.cs b/UnitTests/Configuration/SettingsScopeTests.cs
index 09dac60243..c64d715c26 100644
--- a/UnitTests/Configuration/SettingsScopeTests.cs
+++ b/UnitTests/Configuration/SettingsScopeTests.cs
@@ -34,7 +34,7 @@ public void GetHardCodedDefaults_ShouldSetProperties ()
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Apply_ShouldApplyProperties ()
{
// arrange
@@ -58,7 +58,7 @@ public void Apply_ShouldApplyProperties ()
Assert.True (Application.IsMouseDisabled);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void CopyUpdatedPropertiesFrom_ShouldCopyChangedPropertiesOnly ()
{
Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.End);
diff --git a/UnitTests/Drawing/AttributeTests.cs b/UnitTests/Drawing/AttributeTests.cs
index 96d091fc74..5fbda28e8f 100644
--- a/UnitTests/Drawing/AttributeTests.cs
+++ b/UnitTests/Drawing/AttributeTests.cs
@@ -1,14 +1,17 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Terminal.Gui;
using Xunit;
-
// Alias Console to MockConsole so we don't accidentally use Console
using Console = Terminal.Gui.FakeConsole;
namespace Terminal.Gui.DrawingTests;
+
public class AttributeTests {
+
+ [Fact]
+ public void Attribute_Is_Value_Type () =>
+ // prove that Color is a value type
+ Assert.True (typeof (Attribute).IsValueType);
+
+
[Fact]
public void DefaultConstructor ()
{
@@ -47,7 +50,8 @@ public void ColorNamesConstructor ()
Assert.Equal (new Color (Color.Blue), attribute.Background);
}
- [Fact, AutoInitShutdown]
+ [Fact]
+ [AutoInitShutdown]
public void ColorConstructor ()
{
// Arrange & Act
@@ -60,7 +64,8 @@ public void ColorConstructor ()
Assert.Equal (backgroundColor, attribute.Background);
}
- [Fact, AutoInitShutdown]
+ [Fact]
+ [AutoInitShutdown]
public void ColorAndColorNamesConstructor ()
{
// Arrange & Act
@@ -207,7 +212,7 @@ public void Implicit_Assign ()
// Test conversion to int
attr = new Attribute (value, fg, bg);
- int value_implicit = attr.PlatformColor;
+ var value_implicit = attr.PlatformColor;
Assert.Equal (value, value_implicit);
Assert.Equal (value, attr.PlatformColor);
@@ -368,83 +373,4 @@ public void ToString_ShouldReturnFormattedStringWithForegroundAndBackground ()
// Assert
Assert.Equal (expectedString, attributeString);
}
-
- [Fact]
- public void Changing_One_Default_Reference_Also_Change_All_References_But_Not_A_Instance_Reference ()
- {
- // Make two local attributes, and grab Attribute.Default, which is a reference to a static.
- Attribute attr1 = Attribute.Default;
- Attribute attr2 = Attribute.Default;
- // Make one local attributes, and grab Attribute(), which is a reference to a singleton.
- Attribute attr3 = new Attribute (); // instance
-
- // Assert the starting state that is expected
- Assert.Equal (ColorName.White, attr1.Foreground.ColorName);
- Assert.Equal (ColorName.White, attr2.Foreground.ColorName);
- Assert.Equal (ColorName.White, Attribute.Default.Foreground.ColorName);
- Assert.Equal (ColorName.White, attr3.Foreground.ColorName);
-
- // Now set Foreground.ColorName to ColorName.Blue on one of our local attributes
- attr1.Foreground.ColorName = ColorName.Blue;
-
- // Assert the newly-expected case
- // The last two assertions will fail, because we have actually modified a singleton
- Assert.Equal (ColorName.Blue, attr1.Foreground.ColorName);
- Assert.Equal (ColorName.Blue, attr2.Foreground.ColorName);
- Assert.Equal (ColorName.Blue, Attribute.Default.Foreground.ColorName);
- Assert.Equal (ColorName.White, attr3.Foreground.ColorName);
-
- // Now set Foreground.ColorName to ColorName.Red on the singleton of our local attributes
- attr3.Foreground.ColorName = ColorName.Red;
-
- // Assert the newly-expected case
- // The assertions will not fail, because we have actually modified a singleton
- Assert.Equal (ColorName.Blue, attr1.Foreground.ColorName);
- Assert.Equal (ColorName.Blue, attr2.Foreground.ColorName);
- Assert.Equal (ColorName.Blue, Attribute.Default.Foreground.ColorName);
- Assert.Equal (ColorName.Red, attr3.Foreground.ColorName);
-
- // Now set Foreground.ColorName to ColorName.White on the static of our local attributes
- // This also avoids errors on others unit test when the default is changed
- Attribute.Default.Foreground.ColorName = ColorName.White;
-
- // Assert the newly-expected case
- // The assertions will not fail, because we have actually modified the static default reference
- Assert.Equal (ColorName.White, attr1.Foreground.ColorName);
- Assert.Equal (ColorName.White, attr2.Foreground.ColorName);
- Assert.Equal (ColorName.White, Attribute.Default.Foreground.ColorName);
- Assert.Equal (ColorName.Red, attr3.Foreground.ColorName);
- }
-
- [Fact]
- public void Changing_One_Instance_Reference_Does_Not_Change_All_Instance_References ()
- {
- // Make two local attributes, and grab Attribute (), which are a reference to a singleton.
- Attribute attr1 = new Attribute ();
- // Make two local attributes, and grab Attribute (Int), which are a reference to a singleton.
- Attribute attr2 = new Attribute (-1);
-
- // Assert the starting state that is expected
- Assert.Equal (ColorName.White, attr1.Foreground.ColorName);
- Assert.Equal (ColorName.White, attr2.Foreground.ColorName);
- Assert.Equal (ColorName.White, Attribute.Default.Foreground.ColorName);
-
- // Now set Foreground.ColorName to ColorName.Blue on one of our local attributes
- attr1.Foreground.ColorName = ColorName.Blue;
-
- // Assert the newly-expected case
- // The assertions will not fail, because we have actually modified a singleton
- Assert.Equal (ColorName.Blue, attr1.Foreground.ColorName);
- Assert.Equal (ColorName.White, attr2.Foreground.ColorName);
- Assert.Equal (ColorName.White, Attribute.Default.Foreground.ColorName);
-
- // Now set Foreground.ColorName to ColorName.Red on the other singleton of our local attributes
- attr2.Foreground.ColorName = ColorName.Red;
-
- // Assert the newly-expected case
- // The assertions will not fail, because we have actually modified a singleton
- Assert.Equal (ColorName.Blue, attr1.Foreground.ColorName);
- Assert.Equal (ColorName.Red, attr2.Foreground.ColorName);
- Assert.Equal (ColorName.White, Attribute.Default.Foreground.ColorName);
- }
-}
+}
\ No newline at end of file
diff --git a/UnitTests/Drawing/ColorTests.cs b/UnitTests/Drawing/ColorTests.cs
index c0b3fb60ee..8070f5fab2 100644
--- a/UnitTests/Drawing/ColorTests.cs
+++ b/UnitTests/Drawing/ColorTests.cs
@@ -1,18 +1,27 @@
-using Terminal.Gui;
-using System;
-using System.Buffers;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Globalization;
+using System;
using System.Linq;
-using System.Text;
-using System.Text.Json;
using Xunit;
-using static Unix.Terminal.Curses;
+
namespace Terminal.Gui.DrawingTests;
public class ColorTests {
+ [Fact]
+ public void Color_Is_Value_Type () =>
+ // prove that Color is a value type
+ Assert.True (typeof (Color).IsValueType);
+
+ [Fact]
+ public void Colors_ColorSchemes_Property_Has_Private_Setter ()
+ {
+ // Resharper Code Cleanup likes to remove the `private set; `
+ // from the ColorSchemes property. This test will fail if
+ // that happens.
+ var property = typeof (Colors).GetProperty ("ColorSchemes");
+ Assert.NotNull (property);
+ Assert.NotNull (property.SetMethod);
+ Assert.True (property.GetSetMethod (true).IsPrivate);
+
+ }
[Fact, AutoInitShutdown]
public void ColorScheme_New ()
@@ -26,10 +35,10 @@ public void ColorScheme_New ()
[Fact]
public void TestAllColors ()
{
- var colorNames = Enum.GetValues (typeof (ColorName)).Cast ().Distinct ().ToList();
- Attribute [] attrs = new Attribute [colorNames.Count];
+ var colorNames = Enum.GetValues (typeof (ColorName)).Cast ().Distinct ().ToList ();
+ var attrs = new Attribute [colorNames.Count];
- int idx = 0;
+ var idx = 0;
foreach (ColorName bg in colorNames) {
attrs [idx] = new Attribute (bg, colorNames.Count - 1 - bg);
idx++;
@@ -52,12 +61,9 @@ public void TestAllColors ()
Assert.Equal (new Attribute (Color.BrightYellow, Color.Blue), attrs [14]);
Assert.Equal (new Attribute (Color.White, Color.Black), attrs [^1]);
}
-
+
[Fact]
- public void ColorNames_HasOnly16DistinctElements ()
- {
- Assert.Equal (16, Enum.GetValues (typeof (ColorName)).Cast ().Distinct ().Count ());
- }
+ public void ColorNames_HasOnly16DistinctElements () => Assert.Equal (16, Enum.GetValues (typeof (ColorName)).Cast ().Distinct ().Count ());
[Fact]
public void ColorNames_HaveCorrectOrdinals ()
@@ -84,9 +90,9 @@ public void ColorNames_HaveCorrectOrdinals ()
public void Color_Constructor_WithRGBValues ()
{
// Arrange
- int expectedR = 255;
- int expectedG = 0;
- int expectedB = 128;
+ var expectedR = 255;
+ var expectedG = 0;
+ var expectedB = 128;
// Act
var color = new Color (expectedR, expectedG, expectedB);
@@ -102,10 +108,10 @@ public void Color_Constructor_WithRGBValues ()
public void Color_Constructor_WithAlphaAndRGBValues ()
{
// Arrange
- int expectedA = 128;
- int expectedR = 255;
- int expectedG = 0;
- int expectedB = 128;
+ var expectedA = 128;
+ var expectedR = 255;
+ var expectedG = 0;
+ var expectedB = 128;
// Act
var color = new Color (expectedR, expectedG, expectedB, expectedA);
@@ -121,7 +127,7 @@ public void Color_Constructor_WithAlphaAndRGBValues ()
public void Color_Constructor_WithRgbaValue ()
{
// Arrange
- int expectedRgba = unchecked((int)0xFF804040); // R: 128, G: 64, B: 64, Alpha: 255
+ var expectedRgba = unchecked((int)0xFF804040); // R: 128, G: 64, B: 64, Alpha: 255
// Act
var color = new Color (expectedRgba);
@@ -137,7 +143,7 @@ public void Color_Constructor_WithRgbaValue ()
public void Color_Constructor_WithColorName ()
{
// Arrange
- ColorName colorName = ColorName.Blue;
+ var colorName = ColorName.Blue;
var expectedColor = new Color (0, 55, 218); // Blue
// Act
@@ -151,10 +157,10 @@ public void Color_Constructor_WithColorName ()
public void Color_ToString_WithNamedColor ()
{
// Arrange
- Color color = new Color (0, 55, 218); // Blue
+ var color = new Color (0, 55, 218); // Blue
// Act
- string colorString = color.ToString ();
+ var colorString = color.ToString ();
// Assert
Assert.Equal ("Blue", colorString);
@@ -164,10 +170,10 @@ public void Color_ToString_WithNamedColor ()
public void Color_ToString_WithRGBColor ()
{
// Arrange
- Color color = new Color (1, 64, 32); // Custom RGB color
+ var color = new Color (1, 64, 32); // Custom RGB color
// Act
- string colorString = color.ToString ();
+ var colorString = color.ToString ();
// Assert
Assert.Equal ("#014020", colorString);
@@ -177,7 +183,7 @@ public void Color_ToString_WithRGBColor ()
public void Color_ImplicitOperator_FromInt ()
{
// Arrange
- int Rgba = unchecked((int)0xFF804020); // R: 128, G: 64, B: 32, Alpha: 255
+ var Rgba = unchecked((int)0xFF804020); // R: 128, G: 64, B: 32, Alpha: 255
var expectedColor = new Color (128, 64, 32);
// Act
@@ -192,10 +198,10 @@ public void Color_ExplicitOperator_ToInt ()
{
// Arrange
var color = new Color (128, 64, 32);
- int expectedRgba = unchecked((int)0xFF804020); // R: 128, G: 64, B: 32, Alpha: 255
+ var expectedRgba = unchecked((int)0xFF804020); // R: 128, G: 64, B: 32, Alpha: 255
// Act
- int Rgba = (int)color;
+ var Rgba = (int)color;
// Assert
Assert.Equal (expectedRgba, Rgba);
@@ -206,11 +212,11 @@ public void Color_ExplicitOperator_ToInt ()
public void Color_ImplicitOperator_FromColorNames ()
{
// Arrange
- ColorName colorName = ColorName.Blue;
+ var colorName = ColorName.Blue;
var expectedColor = new Color (0, 55, 218); // Blue
// Act
- Color color = new Color (colorName);
+ var color = new Color (colorName);
// Assert
Assert.Equal (expectedColor, color);
@@ -221,10 +227,10 @@ public void Color_ExplicitOperator_ToColorNames ()
{
// Arrange
var color = new Color (0, 0, 0x80); // Blue
- ColorName expectedColorName = ColorName.Blue;
+ var expectedColorName = ColorName.Blue;
// Act
- ColorName colorName = (ColorName)color;
+ var colorName = (ColorName)color;
// Assert
Assert.Equal (expectedColorName, colorName);
@@ -321,15 +327,14 @@ public void Color_ColorName_Get_ReturnsClosestColorName ()
public void FindClosestColor_ReturnsClosestColor ()
{
// Test cases with RGB values and expected closest color names
- var testCases = new []
- {
- (new Color(0, 0, 0), ColorName.Black),
- (new Color(255, 255, 255), ColorName.White),
- (new Color(5, 100, 255), ColorName.BrightBlue),
- (new Color(0, 255, 0), ColorName.BrightGreen),
- (new Color(255, 70, 8), ColorName.BrightRed),
- (new Color(0, 128, 128), ColorName.Cyan),
- (new Color(128, 64, 32), ColorName.Yellow),
+ var testCases = new [] {
+ (new Color (0, 0, 0), ColorName.Black),
+ (new Color (255, 255, 255), ColorName.White),
+ (new Color (5, 100, 255), ColorName.BrightBlue),
+ (new Color (0, 255, 0), ColorName.BrightGreen),
+ (new Color (255, 70, 8), ColorName.BrightRed),
+ (new Color (0, 128, 128), ColorName.Cyan),
+ (new Color (128, 64, 32), ColorName.Yellow)
};
foreach (var testCase in testCases) {
@@ -341,20 +346,4 @@ public void FindClosestColor_ReturnsClosestColor ()
Assert.Equal (expectedColorName, actualColorName);
}
}
-
- [Fact]
- public void Color_ColorName_Set_SetsColorBasedOnColorName ()
- {
- // Arrange
- var color = new Color (0, 0, 0); // Black
- var expectedColor = new Color (ColorName.Magenta);
-
- // Act
- color.ColorName = ColorName.Magenta;
-
- // Assert
- Assert.Equal (expectedColor, color);
- }
-}
-
-
+}
\ No newline at end of file
diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs
index 0573b250fa..64820150a9 100644
--- a/UnitTests/View/ViewTests.cs
+++ b/UnitTests/View/ViewTests.cs
@@ -479,7 +479,7 @@ public void LabelChangeText_RendersCorrectly_Constructors (int choice)
}
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Internal_Tests ()
{
Assert.Equal (new [] { View.Direction.Forward, View.Direction.Backward },
@@ -569,7 +569,7 @@ public void Internal_Tests ()
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Visible_Sets_Also_Sets_Subviews ()
{
var button = new Button ("Click Me");
@@ -637,7 +637,7 @@ int RunesCount ()
}
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void GetTopSuperView_Test ()
{
var v1 = new View ();
@@ -673,7 +673,7 @@ public void GetTopSuperView_Test ()
top2.Dispose ();
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Clear_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
{
var view = new FrameView {
@@ -721,7 +721,7 @@ public void Clear_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
Assert.Equal (Rect.Empty, pos);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Clear_Bounds_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
{
var view = new FrameView {
@@ -784,7 +784,7 @@ public void IsAdded_Added_Removed ()
view.Dispose ();
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Visible_Clear_The_View_Output ()
{
var view = new View ("Testing visibility."); // use View, not Label to avoid AutoSize == true
@@ -820,7 +820,7 @@ public void Visible_Clear_The_View_Output ()
Application.End (rs);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void DrawContentComplete_Event_Is_Always_Called ()
{
var viewCalled = false;
@@ -838,7 +838,7 @@ public void DrawContentComplete_Event_Is_Always_Called ()
Assert.True (tvCalled);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void GetNormalColor_ColorScheme ()
{
var view = new View { ColorScheme = Colors.Base };
@@ -850,7 +850,7 @@ public void GetNormalColor_ColorScheme ()
view.Dispose ();
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void GetHotNormalColor_ColorScheme ()
{
var view = new View { ColorScheme = Colors.Base };
@@ -926,7 +926,7 @@ public void Clear_Does_Not_Spillover_Its_Parent (bool label)
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Correct_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame ()
{
var label = new Label ("At 0,0");
@@ -960,7 +960,7 @@ A text with some long width
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Correct_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim ()
{
var label = new Label ("At 0,0");
@@ -996,7 +996,7 @@ A text with some long width
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Incorrect_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame ()
{
var label = new Label ("At 0,0");
@@ -1032,7 +1032,7 @@ A text with some long width
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Incorrect_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim ()
{
var label = new Label ("At 0,0");
@@ -1070,7 +1070,7 @@ A text with some long width
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Correct_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame ()
{
var label = new Label ("At 0,0");
@@ -1105,7 +1105,7 @@ A text with some long width
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Correct_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim ()
{
var label = new Label ("At 0,0");
@@ -1143,7 +1143,7 @@ A text with some long width
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Incorrect_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame ()
{
var label = new Label ("At 0,0");
@@ -1177,7 +1177,7 @@ A text with some long width
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Incorrect_Redraw_Bounds_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim ()
{
var label = new Label ("At 0,0");
@@ -1215,7 +1215,7 @@ A text with some long width
Application.End (runState);
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Test_Nested_Views_With_Height_Equal_To_One ()
{
var v = new View { Width = 11, Height = 3, ColorScheme = new ColorScheme () };
@@ -1245,7 +1245,7 @@ public void Test_Nested_Views_With_Height_Equal_To_One ()
bottom.Dispose ();
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Frame_Set_After_Initialize_Update_NeededDisplay ()
{
var frame = new FrameView ();
diff --git a/UnitTests/Views/OverlappedTests.cs b/UnitTests/Views/OverlappedTests.cs
index efb482586b..c2ecedbda2 100644
--- a/UnitTests/Views/OverlappedTests.cs
+++ b/UnitTests/Views/OverlappedTests.cs
@@ -47,7 +47,7 @@ public void Dispose_Toplevel_IsOverlappedContainer_True_With_Begin ()
Application.Shutdown ();
}
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Application_RequestStop_With_Params_On_A_Not_OverlappedContainer_Always_Use_Application_Current ()
{
var top1 = new Toplevel ();
@@ -683,7 +683,7 @@ public void AllChildClosed_Event_Test ()
[Fact]
public void MoveToOverlappedChild_Throw_NullReferenceException_Passing_Null_Parameter () => Assert.Throws (delegate { Application.MoveToOverlappedChild (null); });
- [Fact] [AutoInitShutdown]
+ [Fact, AutoInitShutdown]
public void Visible_False_Does_Not_Clear ()
{
var overlapped = new Overlapped ();
diff --git a/UnitTests/Views/TreeViewTests.cs b/UnitTests/Views/TreeViewTests.cs
index 3e3747ed04..bfce38cf6e 100644
--- a/UnitTests/Views/TreeViewTests.cs
+++ b/UnitTests/Views/TreeViewTests.cs
@@ -1,1286 +1,1286 @@
using System.Collections.Generic;
using System.Linq;
+using System.Text;
using Xunit;
using Xunit.Abstractions;
-namespace Terminal.Gui.ViewsTests {
+namespace Terminal.Gui.ViewsTests;
- public class TreeViewTests {
+public class TreeViewTests {
- readonly ITestOutputHelper output;
+ readonly ITestOutputHelper _output;
- public TreeViewTests (ITestOutputHelper output)
- {
- this.output = output;
- }
+ public TreeViewTests (ITestOutputHelper output) => _output = output;
- #region Test Setup Methods
- class Factory {
- public Car [] Cars { get; set; }
- public override string ToString ()
- {
- return "Factory";
- }
- };
- class Car {
- public string Name { get; set; }
- public override string ToString ()
- {
- return Name;
- }
- };
+ ///
+ /// Tests that and are consistent
+ ///
+ [Fact]
+ public void IsExpanded_TrueAfterExpand ()
+ {
+ var tree = CreateTree (out var f, out _, out _);
+ Assert.False (tree.IsExpanded (f));
+
+ tree.Expand (f);
+ Assert.True (tree.IsExpanded (f));
+
+ tree.Collapse (f);
+ Assert.False (tree.IsExpanded (f));
+ }
+
+ [Fact]
+ public void EmptyTreeView_ContentSizes ()
+ {
+ var emptyTree = new TreeView ();
+ Assert.Equal (0, emptyTree.ContentHeight);
+ Assert.Equal (0, emptyTree.GetContentWidth (true));
+ Assert.Equal (0, emptyTree.GetContentWidth (false));
+ }
+
+ [Fact]
+ public void EmptyTreeViewGeneric_ContentSizes ()
+ {
+ var emptyTree = new TreeView ();
+ Assert.Equal (0, emptyTree.ContentHeight);
+ Assert.Equal (0, emptyTree.GetContentWidth (true));
+ Assert.Equal (0, emptyTree.GetContentWidth (false));
+ }
+
+ ///
+ /// Tests that results in a correct content height
+ ///
+ [Fact]
+ public void ContentHeight_BiggerAfterExpand ()
+ {
+ var tree = CreateTree (out var f, out _, out _);
+ Assert.Equal (1, tree.ContentHeight);
+
+ tree.Expand (f);
+ Assert.Equal (3, tree.ContentHeight);
+
+ tree.Collapse (f);
+ Assert.Equal (1, tree.ContentHeight);
+ }
+
+ [Fact]
+ public void ContentWidth_BiggerAfterExpand ()
+ {
+ var tree = CreateTree (out var f, out var car1, out _);
+ tree.BeginInit ();
+ tree.EndInit ();
+
+ tree.Bounds = new Rect (0, 0, 10, 10);
+
+ InitFakeDriver ();
+
+ //-+Factory
+ Assert.Equal (9, tree.GetContentWidth (true));
+
+ car1.Name = "123456789";
+
+ tree.Expand (f);
+
+ //..├-123456789
+ Assert.Equal (13, tree.GetContentWidth (true));
+
+ tree.Collapse (f);
+ //-+Factory
+ Assert.Equal (9, tree.GetContentWidth (true));
+
+ Application.Shutdown ();
+ }
+
+ [Fact]
+ public void ContentWidth_VisibleVsAll ()
+ {
+ var tree = CreateTree (out var f, out var car1, out var car2);
+ tree.BeginInit ();
+ tree.EndInit ();
+
+ // control only allows 1 row to be viewed at once
+ tree.Bounds = new Rect (0, 0, 20, 1);
+
+ InitFakeDriver ();
+
+ //-+Factory
+ Assert.Equal (9, tree.GetContentWidth (true));
+ Assert.Equal (9, tree.GetContentWidth (false));
+
+ car1.Name = "123456789";
+ car2.Name = "12345678";
+
+ tree.Expand (f);
+
+ // Although expanded the bigger (longer) child node is not in the rendered area of the control
+ Assert.Equal (9, tree.GetContentWidth (true));
+ Assert.Equal (13, tree.GetContentWidth (false)); // If you ask for the global max width it includes the longer child
+
+ // Now that we have scrolled down 1 row we should see the big child
+ tree.ScrollOffsetVertical = 1;
+ Assert.Equal (13, tree.GetContentWidth (true));
+ Assert.Equal (13, tree.GetContentWidth (false));
+
+ // Scroll down so only car2 is visible
+ tree.ScrollOffsetVertical = 2;
+ Assert.Equal (12, tree.GetContentWidth (true));
+ Assert.Equal (13, tree.GetContentWidth (false));
+
+ // Scroll way down (off bottom of control even)
+ tree.ScrollOffsetVertical = 5;
+ Assert.Equal (0, tree.GetContentWidth (true));
+ Assert.Equal (13, tree.GetContentWidth (false));
+
+ Application.Shutdown ();
+ }
+
+ ///
+ /// Tests that and behaves correctly when
+ /// an object cannot be expanded (because it has no children)
+ ///
+ [Fact]
+ public void IsExpanded_FalseIfCannotExpand ()
+ {
+ var tree = CreateTree (out var f, out var c, out _);
+
+ // expose the car by expanding the factory
+ tree.Expand (f);
+
+ // car is not expanded
+ Assert.False (tree.IsExpanded (c));
+
+ //try to expand the car (should have no effect because cars have no children)
+ tree.Expand (c);
+
+ Assert.False (tree.IsExpanded (c));
- private TreeView