diff --git a/.editorconfig b/.editorconfig
index 65ef50ca8..a1afa2b5a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -26,31 +26,6 @@ csharp_space_between_method_declaration_name_and_open_parenthesis = true
csharp_style_var_elsewhere = true:none
# ReSharper properties
-resharper_align_linq_query = true
-resharper_align_multiline_binary_patterns = true
-resharper_align_multiline_calls_chain = true
-resharper_align_multiline_extends_list = true
-resharper_align_multiline_parameter = true
-resharper_blank_lines_around_region = 1
-resharper_braces_redundant = true
-resharper_csharp_alignment_tab_fill_style = optimal_fill
-resharper_csharp_max_line_length = 200
-resharper_csharp_stick_comment = false
-resharper_csharp_wrap_parameters_style = chop_if_long
-resharper_force_attribute_style = separate
-resharper_indent_type_constraints = true
-resharper_xmldoc_indent_size = 2
-resharper_xmldoc_indent_style = space
-resharper_xmldoc_tab_width = 2
-#resharper_int_align_binary_expressions = true
-resharper_int_align_comments = true
-resharper_int_align_invocations = true
-resharper_int_align_nested_ternary = true
-resharper_int_align_switch_expressions = true
-resharper_int_align_switch_sections = true
-resharper_local_function_body = expression_body
-resharper_remove_blank_lines_near_braces_in_declarations = true
-resharper_use_roslyn_logic_for_evident_types = true
csharp_space_around_binary_operators = before_and_after
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
@@ -96,10 +71,6 @@ csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_var_for_built_in_types = true:none
-resharper_wrap_before_linq_expression = true
-resharper_wrap_chained_binary_expressions = chop_if_long
-resharper_wrap_chained_binary_patterns = chop_if_long
-
[*.{cs,vb}]
dotnet_style_operator_placement_when_wrapping = beginning_of_line
diff --git a/ReactiveExample/LoginView.cs b/ReactiveExample/LoginView.cs
index a74fb9f94..eb9fc1378 100644
--- a/ReactiveExample/LoginView.cs
+++ b/ReactiveExample/LoginView.cs
@@ -122,7 +122,7 @@ namespace ReactiveExample {
.DisposeWith (_disposable);
ViewModel
.WhenAnyValue (x => x.IsValid)
- .Select (valid => valid ? Colors.Base : Colors.Error)
+ .Select (valid => valid ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"])
.BindTo (validationLabel, x => x.ColorScheme)
.DisposeWith (_disposable);
Add (validationLabel);
diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs
index 95a15e457..58bfddd4d 100644
--- a/Terminal.Gui/Application.cs
+++ b/Terminal.Gui/Application.cs
@@ -32,6 +32,79 @@ namespace Terminal.Gui;
/// TODO: Flush this out.
///
public static partial class Application {
+
+ // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
+ // Encapsulate all setting of initial state for Application; Having
+ // this in a function like this ensures we don't make mistakes in
+ // guaranteeing that the state of this singleton is deterministic when Init
+ // starts running and after Shutdown returns.
+ internal static void ResetState ()
+ {
+ // Shutdown is the bookend for Init. As such it needs to clean up all resources
+ // Init created. Apps that do any threading will need to code defensively for this.
+ // e.g. see Issue #537
+ foreach (var t in _topLevels) {
+ t.Running = false;
+ t.Dispose ();
+ }
+ _topLevels.Clear ();
+ Current = null;
+ Top?.Dispose ();
+ Top = null;
+
+ // MainLoop stuff
+ MainLoop?.Dispose ();
+ MainLoop = null;
+ _mainThreadId = -1;
+ Iteration = null;
+ EndAfterFirstIteration = false;
+
+ // Driver stuff
+ if (Driver != null) {
+ Driver.SizeChanged -= Driver_SizeChanged;
+ Driver.KeyDown -= Driver_KeyDown;
+ Driver.KeyUp -= Driver_KeyUp;
+ Driver.MouseEvent -= Driver_MouseEvent;
+ Driver?.End ();
+ Driver = null;
+ }
+ // Don't reset ForceDriver; it needs to be set before Init is called.
+ //ForceDriver = string.Empty;
+ Force16Colors = false;
+ _forceFakeConsole = false;
+
+ // Run State stuff
+ NotifyNewRunState = null;
+ NotifyStopRunState = null;
+ MouseGrabView = null;
+ _initialized = false;
+
+ // Mouse
+ _mouseEnteredView = null;
+ WantContinuousButtonPressedView = null;
+ MouseEvent = null;
+ GrabbedMouse = null;
+ UnGrabbingMouse = null;
+ GrabbedMouse = null;
+ UnGrabbedMouse = null;
+
+ // Keyboard
+ AlternateBackwardKey = Key.Empty;
+ AlternateForwardKey = Key.Empty;
+ QuitKey = Key.Empty;
+ KeyDown = null;
+ KeyUp = null;
+ SizeChanging = null;
+
+ Colors.Reset ();
+
+ // Reset synchronization context to allow the user to run async/await,
+ // as the main loop has been ended, the synchronization context from
+ // gui.cs does no longer process any callbacks. See #1084 for more details:
+ // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
+ SynchronizationContext.SetSynchronizationContext (syncContext: null);
+ }
+
///
/// Gets the that has been selected. See also .
///
@@ -66,7 +139,7 @@ public static partial class Application {
///
public static List SupportedCultures => _cachedSupportedCultures;
- static List GetSupportedCultures ()
+ internal static List GetSupportedCultures ()
{
var culture = CultureInfo.GetCultures (CultureTypes.AllCultures);
@@ -109,7 +182,7 @@ public static partial class Application {
///
/// The to use. If neither or are specified the default driver for the platform will be used.
/// The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the to use. If neither or are specified the default driver for the platform will be used.
- public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (() => new Toplevel(), driver, driverName);
+ public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (() => new Toplevel (), driver, driverName);
internal static bool _initialized = false;
internal static int _mainThreadId = -1;
@@ -196,8 +269,8 @@ public static partial class Application {
Current = Top;
// Ensure Top's layout is up to date.
- Current.SetRelativeLayout (Driver.Bounds);
-
+ Current.SetRelativeLayout (Driver.Bounds);
+
_cachedSupportedCultures = GetSupportedCultures ();
_mainThreadId = Thread.CurrentThread.ManagedThreadId;
_initialized = true;
@@ -241,55 +314,6 @@ public static partial class Application {
ResetState ();
PrintJsonErrors ();
}
-
- // Encapsulate all setting of initial state for Application; Having
- // this in a function like this ensures we don't make mistakes in
- // guaranteeing that the state of this singleton is deterministic when Init
- // starts running and after Shutdown returns.
- static void ResetState ()
- {
- // Shutdown is the bookend for Init. As such it needs to clean up all resources
- // Init created. Apps that do any threading will need to code defensively for this.
- // e.g. see Issue #537
- foreach (var t in _topLevels) {
- t.Running = false;
- t.Dispose ();
- }
- _topLevels.Clear ();
- Current = null;
- Top?.Dispose ();
- Top = null;
-
- // BUGBUG: OverlappedTop is not cleared here, but it should be?
-
- MainLoop?.Dispose ();
- MainLoop = null;
- if (Driver != null) {
- Driver.SizeChanged -= Driver_SizeChanged;
- Driver.KeyDown -= Driver_KeyDown;
- Driver.KeyUp -= Driver_KeyUp;
- Driver.MouseEvent -= Driver_MouseEvent;
- Driver?.End ();
- Driver = null;
- }
- Iteration = null;
- MouseEvent = null;
- KeyDown = null;
- KeyUp = null;
- SizeChanging = null;
- _mainThreadId = -1;
- NotifyNewRunState = null;
- NotifyStopRunState = null;
- _initialized = false;
- MouseGrabView = null;
- _mouseEnteredView = null;
-
- // Reset synchronization context to allow the user to run async/await,
- // as the main loop has been ended, the synchronization context from
- // gui.cs does no longer process any callbacks. See #1084 for more details:
- // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
- SynchronizationContext.SetSynchronizationContext (syncContext: null);
- }
#endregion Initialization (Init/Shutdown)
#region Run (Begin, Run, End, Stop)
@@ -402,7 +426,7 @@ public static partial class Application {
}
//if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
- Toplevel.SetRelativeLayout (Driver.Bounds);
+ Toplevel.SetRelativeLayout (Driver.Bounds);
//}
Toplevel.LayoutSubviews ();
Toplevel.PositionToplevels ();
@@ -447,7 +471,7 @@ public static partial class Application {
/// platform will be used (, , or ).
/// Must be if has already been called.
///
- public static void Run (Func errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new ()
+ public static void Run (Func errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new()
{
if (_initialized) {
if (Driver != null) {
@@ -881,7 +905,7 @@ public static partial class Application {
///
// BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What
// about TopLevels that are just a SubView of another View?
- static readonly Stack _topLevels = new Stack ();
+ internal static readonly Stack _topLevels = new Stack ();
///
/// The object used for the application on startup ()
@@ -1141,7 +1165,7 @@ public static partial class Application {
}
// Used by OnMouseEvent to track the last view that was clicked on.
- static View _mouseEnteredView;
+ internal static View _mouseEnteredView;
///
/// Event fired when a mouse move or click occurs. Coordinates are screen relative.
@@ -1227,7 +1251,7 @@ public static partial class Application {
}
}
- bool FrameHandledMouseEvent (Frame frame)
+ bool FrameHandledMouseEvent (Adornment frame)
{
if (frame?.Thickness.Contains (frame.FrameToScreen (), a.MouseEvent.X, a.MouseEvent.Y) ?? false) {
var boundsPoint = frame.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y);
@@ -1333,12 +1357,13 @@ public static partial class Application {
#endregion Mouse handling
#region Keyboard handling
- static Key _alternateForwardKey = new Key (KeyCode.PageDown | KeyCode.CtrlMask);
+ static Key _alternateForwardKey = Key.Empty; // Defined in config.json
///
/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
///
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ [JsonConverter (typeof (KeyJsonConverter))]
public static Key AlternateForwardKey {
get => _alternateForwardKey;
set {
@@ -1357,12 +1382,13 @@ public static partial class Application {
}
}
- static Key _alternateBackwardKey = new Key (KeyCode.PageUp | KeyCode.CtrlMask);
+ static Key _alternateBackwardKey = Key.Empty; // Defined in config.json
///
/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
///
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ [JsonConverter (typeof (KeyJsonConverter))]
public static Key AlternateBackwardKey {
get => _alternateBackwardKey;
set {
@@ -1381,12 +1407,13 @@ public static partial class Application {
}
}
- static Key _quitKey = new Key (KeyCode.Q | KeyCode.CtrlMask);
+ static Key _quitKey = Key.Empty; // Defined in config.json
///
/// Gets or sets the key to quit the application.
///
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope))] [JsonConverter (typeof (KeyJsonConverter))]
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ [JsonConverter (typeof (KeyJsonConverter))]
public static Key QuitKey {
get => _quitKey;
set {
diff --git a/Terminal.Gui/Configuration/AttributeJsonConverter.cs b/Terminal.Gui/Configuration/AttributeJsonConverter.cs
index 66d085baf..55bd84575 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 {
+namespace Terminal.Gui;
+
+///
+/// Json converter fro the class.
+///
+class AttributeJsonConverter : JsonConverter {
+ static AttributeJsonConverter _instance;
+
///
- /// Json converter fro the class.
+ ///
///
- class AttributeJsonConverter : JsonConverter {
- private static AttributeJsonConverter 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 static AttributeJsonConverter Instance {
+ get {
+ if (_instance == null) {
+ _instance = new AttributeJsonConverter ();
}
- 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);
- }
-
- if (reader.TokenType != JsonTokenType.PropertyName) {
- throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
- }
-
- string propertyName = reader.GetString ();
- reader.Read ();
- string color = $"\"{reader.GetString ()}\"";
-
- switch (propertyName.ToLower ()) {
- case "foreground":
- foreground = JsonSerializer.Deserialize (color, 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 ();
+ 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}.");
+ }
+
+ 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}.");
+ }
+
+ 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}.");
+ }
+ }
+ 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/ColorSchemeJsonConverter.cs b/Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs
index 6a57de14d..45a316985 100644
--- a/Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs
+++ b/Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs
@@ -28,10 +28,22 @@ namespace Terminal.Gui {
throw new JsonException ($"Unexpected StartObject token when parsing ColorScheme: {reader.TokenType}.");
}
- var colorScheme = new ColorScheme ();
+ Attribute normal = Attribute.Default;
+ Attribute focus = Attribute.Default;
+ Attribute hotNormal = Attribute.Default;
+ Attribute hotFocus = Attribute.Default;
+ Attribute disabled = Attribute.Default;
while (reader.Read ()) {
if (reader.TokenType == JsonTokenType.EndObject) {
+ var colorScheme = new ColorScheme () {
+ Normal = normal,
+ Focus = focus,
+ HotNormal = hotNormal,
+ HotFocus = hotFocus,
+ Disabled = disabled
+ };
+
return colorScheme;
}
@@ -45,19 +57,19 @@ namespace Terminal.Gui {
switch (propertyName.ToLower()) {
case "normal":
- colorScheme.Normal = attribute;
+ normal = attribute;
break;
case "focus":
- colorScheme.Focus = attribute;
+ focus = attribute;
break;
case "hotnormal":
- colorScheme.HotNormal = attribute;
+ hotNormal = attribute;
break;
case "hotfocus":
- colorScheme.HotFocus = attribute;
+ hotFocus = attribute;
break;
case "disabled":
- colorScheme.Disabled = attribute;
+ disabled = attribute;
break;
default:
throw new JsonException ($"Unrecognized ColorScheme Attribute name: {propertyName}.");
diff --git a/Terminal.Gui/Configuration/ConfigProperty.cs b/Terminal.Gui/Configuration/ConfigProperty.cs
index 64b59a43e..8800f4e4f 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 class ConfigProperty {
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 class ConfigProperty {
}
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 class ConfigProperty {
/// 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,14 @@ public class ConfigProperty {
{
if (PropertyValue != null) {
try {
- PropertyInfo?.SetValue (null, ConfigurationManager.DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
+ if (PropertyInfo?.GetValue (null) != 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 +97,10 @@ public class ConfigProperty {
// 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 951084811..977e81d7c 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;
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 @@ using static Terminal.Gui.SpinnerStyle;
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 {
- private static readonly string _configFilename = "config.json";
+ ///
+ /// 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,
- internal static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions {
+ ///
+ /// 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
+
+ }
+
+ static readonly string _configFilename = "config.json";
+
+ 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 partial class ConfigurationManager {
}
return _settings;
}
- set {
- _settings = value!;
- }
+ set => _settings = value!;
}
///
@@ -124,15 +157,34 @@ public static partial class ConfigurationManager {
///
/// 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 GlyphDefinitions ();
+ [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))]
+ 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 @@ public static partial class ConfigurationManager {
_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 @@ public static partial class ConfigurationManager {
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 @@ public static partial class ConfigurationManager {
}
///
- /// 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 @@ public static partial class ConfigurationManager {
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 @@ public static partial class ConfigurationManager {
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 @@ public static partial class ConfigurationManager {
///
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 partial class ConfigurationManager {
///
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 partial class ConfigurationManager {
/// 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 partial class ConfigurationManager {
}
///
- /// 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 partial class ConfigurationManager {
}
///
- /// 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 partial class ConfigurationManager {
{
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 partial class ConfigurationManager {
// 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 partial class ConfigurationManager {
// 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 @@ public static partial class ConfigurationManager {
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/Configuration/ThemeManager.cs b/Terminal.Gui/Configuration/ThemeManager.cs
index bf3cf6dd9..a04b3606b 100644
--- a/Terminal.Gui/Configuration/ThemeManager.cs
+++ b/Terminal.Gui/Configuration/ThemeManager.cs
@@ -133,7 +133,7 @@ public class ThemeManager : IDictionary {
internal static void Reset ()
{
Debug.WriteLine ($"Themes.Reset()");
-
+ Colors.Reset ();
Themes?.Clear ();
SelectedTheme = string.Empty;
}
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
index 70a9a4827..17d0846ae 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
@@ -62,7 +62,7 @@ public abstract class ConsoleDriver {
get => _cols;
internal set {
_cols = value;
- ClearContents();
+ ClearContents ();
}
}
@@ -73,7 +73,7 @@ public abstract class ConsoleDriver {
get => _rows;
internal set {
_rows = value;
- ClearContents();
+ ClearContents ();
}
}
@@ -467,7 +467,7 @@ public abstract class ConsoleDriver {
public virtual Attribute MakeColor (Color foreground, Color background) =>
// Encode the colors into the int value.
new (
- 0, // only used by cursesdriver!
+ -1, // only used by cursesdriver!
foreground,
background
);
@@ -535,13 +535,13 @@ public abstract class ConsoleDriver {
Off = 0b_0000_0000,
///
- /// When enabled, will draw a
+ /// When enabled, will draw a
/// ruler in the frame for any side with a padding value greater than 0.
///
FrameRuler = 0b_0000_0001,
///
- /// When enabled, will draw a
+ /// When enabled, will draw a
/// 'L', 'R', 'T', and 'B' when clearing 's instead of ' '.
///
FramePadding = 0b_0000_0010
diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
index 74a57dd80..81d9b0d21 100644
--- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
@@ -43,6 +43,8 @@ public class FakeDriver : ConsoleDriver {
public FakeDriver ()
{
+ Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
+ Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
if (FakeBehaviors.UseFakeClipboard) {
Clipboard = new FakeClipboard (FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException, FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse);
} else {
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
index ed9f004c7..939c4ee4c 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
@@ -825,7 +825,8 @@ internal class WindowsDriver : ConsoleDriver {
// TODO: if some other Windows-based terminal supports true color, update this logic to not
// force 16color mode (.e.g ConEmu which really doesn't work well at all).
- _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") != null;
+ _isWindowsTerminal = _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") != null ||
+ Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null;
if (!_isWindowsTerminal) {
Force16Colors = true;
}
diff --git a/Terminal.Gui/Drawing/Cell.cs b/Terminal.Gui/Drawing/Cell.cs
index 297726949..0fbbd0e33 100644
--- a/Terminal.Gui/Drawing/Cell.cs
+++ b/Terminal.Gui/Drawing/Cell.cs
@@ -40,4 +40,7 @@ public class Cell {
/// been modified since the last time it was drawn.
///
public bool IsDirty { get; set; }
+
+ ///
+ public override string ToString () => $"[{Rune}, {Attribute}]";
}
diff --git a/Terminal.Gui/Drawing/Color.cs b/Terminal.Gui/Drawing/Color.cs
index bcab0b27f..9a2e8b5e7 100644
--- a/Terminal.Gui/Drawing/Color.cs
+++ b/Terminal.Gui/Drawing/Color.cs
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
@@ -15,82 +14,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,173 +119,91 @@ 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 {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The red 8-bits.
- /// The green 8-bits.
- /// The blue 8-bits.
- /// Optional; defaults to 0xFF. The Alpha channel is not supported by Terminal.Gui.
- public Color (int red, int green, int blue, int alpha = 0xFF)
- {
- R = red;
- G = green;
- B = blue;
- A = alpha;
- }
-
- ///
- /// 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;
-
- ///
- /// Initializes a new instance of the color from a legacy 16-color value.
- ///
- /// The 16-color value.
- public Color (ColorName colorName)
- {
- var c = FromColorName (colorName);
- R = c.R;
- G = c.G;
- B = c.B;
- A = c.A;
- }
-
- ///
- /// Initializes a new instance of the color from string. See for details.
- ///
- ///
- ///
- public Color (string colorString)
- {
- if (!TryParse (colorString, out var c)) {
- throw new ArgumentOutOfRangeException (nameof (colorString));
- }
- R = c.R;
- G = c.G;
- B = c.B;
- A = c.A;
- }
-
- ///
- /// Initializes a new instance of the .
- ///
- public Color ()
- {
- R = 0;
- G = 0;
- B = 0;
- A = 0xFF;
- }
-
- ///
- /// Red color component.
- ///
- public int R { get; set; }
- ///
- /// Green color component.
- ///
- public int G { get; set; }
- ///
- /// Blue color component.
- ///
- public int B { get; set; }
-
- ///
- /// Alpha color component.
- ///
- ///
- /// The Alpha channel is not supported by Terminal.Gui.
- ///
- public int A { get; set; } = 0xFF; // Not currently supported; here for completeness.
-
- ///
- /// Gets or sets the color value encoded as ARGB32.
- ///
- /// (<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);
- }
- }
+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 () {
+ 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 },
@@ -312,6 +247,106 @@ public class Color : IEquatable {
{ ColorName.White, AnsiColorCode.BRIGHT_WHITE }
}.ToImmutableDictionary ();
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The red 8-bits.
+ /// The green 8-bits.
+ /// The blue 8-bits.
+ /// Optional; defaults to 0xFF. The Alpha channel is not supported by Terminal.Gui.
+ public Color (int red, int green, int blue, int alpha = 0xFF)
+ {
+ R = red;
+ G = green;
+ B = blue;
+ A = alpha;
+ }
+
+ ///
+ /// 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)
+ {
+ 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.
+ ///
+ /// The 16-color value.
+ public Color (ColorName colorName)
+ {
+ var c = FromColorName (colorName);
+ R = c.R;
+ G = c.G;
+ B = c.B;
+ A = c.A;
+ }
+
+ ///
+ /// Initializes a new instance of the color from string. See
+ /// for details.
+ ///
+ ///
+ ///
+ public Color (string colorString)
+ {
+ if (!TryParse (colorString, out var c)) {
+ throw new ArgumentOutOfRangeException (nameof (colorString));
+ }
+ R = c.R;
+ G = c.G;
+ B = c.B;
+ A = c.A;
+ }
+
+ ///
+ /// Initializes a new instance of the .
+ ///
+ public Color ()
+ {
+ R = 0;
+ G = 0;
+ B = 0;
+ A = 0xFF;
+ }
+
+ ///
+ /// Red color component.
+ ///
+ public int R { get; }
+
+ ///
+ /// Green color component.
+ ///
+ public int G { get; }
+
+ ///
+ /// Blue color component.
+ ///
+ public int B { get; }
+
+ ///
+ /// Alpha color component.
+ ///
+ ///
+ /// The Alpha channel is not supported by Terminal.Gui.
+ ///
+ public int A { get; } // Not currently supported; here for completeness.
+
+ ///
+ /// Gets or sets the color value encoded as ARGB32.
+ ///
+ /// (<see cref="A"/> << 24) | (<see cref="R"/> << 16) | (<see cref="G"/> << 8) | <see cref="B"/>
+ ///
+ ///
+ [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 +367,28 @@ public class Color : IEquatable {
}
}
+ ///
+ /// 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 +403,10 @@ public class Color : IEquatable {
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 +419,128 @@ public class Color : IEquatable {
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 {
-
- var c = FromColorName (value);
- R = c.R;
- G = c.G;
- B = c.B;
- A = c.A;
+ public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
+ {
+ // empty color
+ if (string.IsNullOrEmpty (text)) {
+ color = new Color ();
+ return false;
}
+
+ // #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,201 +552,111 @@ public class Color : IEquatable {
/// 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.
+ /// Cast to int.
///
///
- public static explicit operator int (Color color) => color.Rgba;
+ public static implicit operator int (Color color) => color.Rgba;
///
- /// Cast from .
+ /// Cast from . May fail if the color is not a named color.
///
///
- public static explicit operator Color (ColorName colorName) => new Color (colorName);
+ public static explicit operator Color (ColorName colorName) => new (colorName);
///
- /// Cast to .
+ /// Cast to . May fail if the color is not a named color.
///
///
public static explicit operator ColorName (Color color) => color.ColorName;
-
///
/// Equality operator for two objects..
///
///
///
///
- 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);
///
/// Inequality operator for two objects.
@@ -610,18 +664,7 @@ public class Color : IEquatable {
///
///
///
- 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 +704,30 @@ public class Color : IEquatable {
///
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 +739,32 @@ public readonly struct Attribute : IEquatable {
/// 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 +774,8 @@ public readonly struct Attribute : IEquatable {
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 +850,7 @@ public readonly struct Attribute : IEquatable {
///
/// 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,261 +872,19 @@ public readonly struct Attribute : IEquatable {
///
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}";
+ // Note: Unit tests are dependent on this format
+ $"[{Foreground},{Background}]";
}
-///
-/// Defines the s for common visible elements in a .
-/// Containers such as and use to determine
-/// the colors used by sub-views.
-///
-///
-/// See also: .
-///
-[JsonConverter (typeof (ColorSchemeJsonConverter))]
-public class ColorScheme : IEquatable {
- Attribute _normal = Attribute.Default;
- Attribute _focus = Attribute.Default;
- Attribute _hotNormal = Attribute.Default;
- Attribute _hotFocus = Attribute.Default;
- Attribute _disabled = Attribute.Default;
-
- ///
- /// Used by and to track which ColorScheme
- /// is being accessed.
- ///
- internal string _schemeBeingSet = "";
-
- ///
- /// Creates a new instance.
- ///
- 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 ()
- {
- if (scheme != null) {
- _normal = scheme.Normal;
- _focus = scheme.Focus;
- _hotNormal = scheme.HotNormal;
- _disabled = scheme.Disabled;
- _hotFocus = scheme.HotFocus;
- }
- }
-
- ///
- /// Creates a new instance, initialized with the values from .
- ///
- /// The attribute to initialize the new instance with.
- public ColorScheme (Attribute attribute)
- {
- _normal = attribute;
- _focus = attribute;
- _hotNormal = attribute;
- _disabled = attribute;
- _hotFocus = attribute;
- }
-
- ///
- /// The foreground and background color for text when the view is not focused, hot, or disabled.
- ///
- public Attribute Normal {
- get => _normal;
- set => _normal = value;
- }
-
- ///
- /// The foreground and background color for text when the view has the focus.
- ///
- public Attribute Focus {
- get => _focus;
- set => _focus = value;
- }
-
- ///
- /// The foreground and background color for text when the view is highlighted (hot).
- ///
- public Attribute HotNormal {
- get => _hotNormal;
- set => _hotNormal = value;
- }
-
- ///
- /// The foreground and background color for text when the view is highlighted (hot) and has focus.
- ///
- public Attribute HotFocus {
- get => _hotFocus;
- set => _hotFocus = value;
- }
-
- ///
- /// The default foreground and background color for text, when the view is disabled.
- ///
- public Attribute Disabled {
- get => _disabled;
- set => _disabled = value;
- }
-
- ///
- /// Compares two objects for equality.
- ///
- ///
- /// true if the two objects are equal
- public override bool Equals (object obj) => Equals (obj as ColorScheme);
-
- ///
- /// 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);
-
- ///
- /// Returns a hashcode for this instance.
- ///
- /// hashcode for this instance
- public override int GetHashCode ()
- {
- int hashCode = -1242460230;
- hashCode = hashCode * -1521134295 + _normal.GetHashCode ();
- hashCode = hashCode * -1521134295 + _focus.GetHashCode ();
- hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode ();
- hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode ();
- hashCode = hashCode * -1521134295 + _disabled.GetHashCode ();
- return hashCode;
- }
-
- ///
- /// Compares two objects for equality.
- ///
- ///
- ///
- /// true if the two objects are equivalent
- public static bool operator == (ColorScheme left, ColorScheme right) => EqualityComparer.Default.Equals (left, right);
-
- ///
- /// Compares two objects for inequality.
- ///
- ///
- ///
- /// true if the two objects are not equivalent
- public static bool operator != (ColorScheme left, ColorScheme right) => !(left == right);
-}
-///
-/// The default s for the application.
-///
-///
-/// 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"];
- ///
- ///
- public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); }
-
- ///
- /// 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"];
- ///
- ///
- public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); }
-
- ///
- /// 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"];
- ///
- ///
- public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); }
-
- ///
- /// The menu bar color
- ///
- ///
- ///
- /// 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); }
-
- ///
- /// The color scheme for showing errors.
- ///
- ///
- ///
- /// 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); }
-
- static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) => ColorSchemes [schemeBeingSet];
-
- static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null)
- {
- ColorSchemes [schemeBeingSet] = colorScheme;
- colorScheme._schemeBeingSet = schemeBeingSet;
- }
-
- ///
- /// Provides the defined s.
- ///
- [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
- [JsonConverter (typeof (DictionaryJsonConverter))]
- public static Dictionary ColorSchemes { get; private set; }
-}
\ No newline at end of file
diff --git a/Terminal.Gui/Drawing/ColorScheme.cs b/Terminal.Gui/Drawing/ColorScheme.cs
new file mode 100644
index 000000000..e988365a1
--- /dev/null
+++ b/Terminal.Gui/Drawing/ColorScheme.cs
@@ -0,0 +1,246 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
+
+///
+/// Defines a standard set of s for common visible elements in a .
+///
+///
+///
+/// ColorScheme objects are immutable. Once constructed, the properties cannot be changed.
+/// To change a ColorScheme, create a new one with the desired values,
+/// using the constructor.
+///
+///
+/// See also: .
+///
+///
+[JsonConverter (typeof (ColorSchemeJsonConverter))]
+public class ColorScheme : IEquatable {
+ readonly Attribute _disabled = Attribute.Default;
+ readonly Attribute _focus = Attribute.Default;
+ readonly Attribute _hotFocus = Attribute.Default;
+ readonly Attribute _hotNormal = Attribute.Default;
+ readonly Attribute _normal = Attribute.Default;
+
+ ///
+ /// Creates a new instance set to the default colors (see ).
+ ///
+ 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)
+ {
+ if (scheme == null) {
+ throw new ArgumentNullException (nameof (scheme));
+ }
+ _normal = scheme.Normal;
+ _focus = scheme.Focus;
+ _hotNormal = scheme.HotNormal;
+ _disabled = scheme.Disabled;
+ _hotFocus = scheme.HotFocus;
+ }
+
+ ///
+ /// Creates a new instance, initialized with the values from .
+ ///
+ /// The attribute to initialize the new instance with.
+ public ColorScheme (Attribute attribute)
+ {
+ _normal = attribute;
+ _focus = attribute;
+ _hotNormal = attribute;
+ _disabled = attribute;
+ _hotFocus = attribute;
+ }
+
+ ///
+ /// The foreground and background color for text when the view is not focused, hot, or disabled.
+ ///
+ public Attribute Normal {
+ get => _normal;
+ init => _normal = value;
+ }
+
+ ///
+ /// The foreground and background color for text when the view has the focus.
+ ///
+ public Attribute Focus {
+ get => _focus;
+ init => _focus = value;
+ }
+
+ ///
+ /// The foreground and background color for text in a non-focused view that indicates a .
+ ///
+ public Attribute HotNormal {
+ get => _hotNormal;
+ init => _hotNormal = value;
+ }
+
+ ///
+ /// The foreground and background color for for text in a focused view that indicates a .
+ ///
+ public Attribute HotFocus {
+ get => _hotFocus;
+ init => _hotFocus = value;
+ }
+
+ ///
+ /// The default foreground and background color for text when the view is disabled.
+ ///
+ public Attribute Disabled {
+ get => _disabled;
+ init => _disabled = value;
+ }
+
+ ///
+ /// 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);
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// true if the two objects are equal
+ public override bool Equals (object obj) => Equals (obj is ColorScheme ? (ColorScheme)obj : default);
+
+ ///
+ /// Returns a hashcode for this instance.
+ ///
+ /// hashcode for this instance
+ public override int GetHashCode ()
+ {
+ var hashCode = -1242460230;
+ hashCode = hashCode * -1521134295 + _normal.GetHashCode ();
+ hashCode = hashCode * -1521134295 + _focus.GetHashCode ();
+ hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode ();
+ hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode ();
+ hashCode = hashCode * -1521134295 + _disabled.GetHashCode ();
+ return hashCode;
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ ///
+ /// true if the two objects are equivalent
+ public static bool operator == (ColorScheme left, ColorScheme right) => EqualityComparer.Default.Equals (left, right);
+
+ ///
+ /// Compares two objects for inequality.
+ ///
+ ///
+ ///
+ /// true if the two objects are not equivalent
+ public static bool operator != (ColorScheme left, ColorScheme right) => !(left == right);
+}
+
+///
+/// Holds the s that define the s that are used by views to render themselves.
+///
+public static class Colors {
+ static Colors () => Reset ();
+ ///
+ /// Gets a dictionary of defined objects.
+ ///
+ ///
+ ///
+ /// The dictionary includes the following keys, by default:
+ ///
+ ///
+ /// Built-in Color Scheme
+ /// Description
+ ///
+ /// -
+ ///
+ /// Base
+ ///
+ ///
+ /// The base color scheme used for most Views.
+ ///
+ ///
+ /// -
+ ///
+ /// TopLevel
+ ///
+ ///
+ /// The application Toplevel color scheme; used for the View.
+ ///
+ ///
+ /// -
+ ///
+ /// Dialog
+ ///
+ ///
+ /// The dialog color scheme; used for , , and other views dialog-like views.
+ ///
+ ///
+ /// -
+ ///
+ /// Menu
+ ///
+ ///
+ /// The menu color scheme; used for , , and .
+ ///
+ ///
+ /// -
+ ///
+ /// Error
+ ///
+ ///
+ /// The color scheme for showing errors, such as in .
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Changing the values of an entry in this dictionary will affect all views that use the scheme.
+ ///
+ ///
+ /// can be used to override the default values for these schemes and add additional schemes.
+ /// See .
+ ///
+ ///
+ [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;)
+
+ ///
+ /// Resets the dictionary to the default values.
+ ///
+ public static Dictionary Reset () =>
+ ColorSchemes = new Dictionary (comparer: new SchemeNameComparerIgnoreCase ()) {
+ { "TopLevel", new ColorScheme () },
+ { "Base", new ColorScheme () },
+ { "Dialog", new ColorScheme () },
+ { "Menu", new ColorScheme () },
+ { "Error", new ColorScheme () },
+ };
+
+ 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/Resources/Strings.Designer.cs b/Terminal.Gui/Resources/Strings.Designer.cs
index 3a77922a0..408e14395 100644
--- a/Terminal.Gui/Resources/Strings.Designer.cs
+++ b/Terminal.Gui/Resources/Strings.Designer.cs
@@ -186,6 +186,15 @@ namespace Terminal.Gui.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Date Picker.
+ ///
+ internal static string dpTitle {
+ get {
+ return ResourceManager.GetString("dpTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Any Files.
///
diff --git a/Terminal.Gui/Resources/Strings.fr-FR.resx b/Terminal.Gui/Resources/Strings.fr-FR.resx
index e6dcfcaea..746c45499 100644
--- a/Terminal.Gui/Resources/Strings.fr-FR.resx
+++ b/Terminal.Gui/Resources/Strings.fr-FR.resx
@@ -177,4 +177,7 @@
Ouvrir
+
+ Sélecteur de Date
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.ja-JP.resx b/Terminal.Gui/Resources/Strings.ja-JP.resx
index 14e5ac1de..4a825c51c 100644
--- a/Terminal.Gui/Resources/Strings.ja-JP.resx
+++ b/Terminal.Gui/Resources/Strings.ja-JP.resx
@@ -273,4 +273,7 @@
{0}で降順ソート (_S)
+
+ 日付ピッカー
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.pt-PT.resx b/Terminal.Gui/Resources/Strings.pt-PT.resx
index bfd60ce15..95cdc8569 100644
--- a/Terminal.Gui/Resources/Strings.pt-PT.resx
+++ b/Terminal.Gui/Resources/Strings.pt-PT.resx
@@ -177,4 +177,7 @@
Abrir
+
+ Seletor de Data
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.resx b/Terminal.Gui/Resources/Strings.resx
index d8159c6e0..1152cfb1d 100644
--- a/Terminal.Gui/Resources/Strings.resx
+++ b/Terminal.Gui/Resources/Strings.resx
@@ -277,4 +277,7 @@
_Sort {0} DESC
+
+ Date Picker
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.zh-Hans.resx b/Terminal.Gui/Resources/Strings.zh-Hans.resx
index da767eaa3..009fdd479 100644
--- a/Terminal.Gui/Resources/Strings.zh-Hans.resx
+++ b/Terminal.Gui/Resources/Strings.zh-Hans.resx
@@ -273,4 +273,7 @@
{0}逆序排序 (_S)
+
+ 日期选择器
+
\ No newline at end of file
diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index af906ecf7..bfe79d389 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -40,7 +40,7 @@
-
+
diff --git a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
index a805b6cf3..4ff5bb2cc 100644
--- a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
+++ b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
@@ -124,7 +124,7 @@ namespace Terminal.Gui {
public override ColorScheme ColorScheme {
get {
if (colorScheme == null) {
- colorScheme = Colors.Menu;
+ colorScheme = Colors.ColorSchemes ["Menu"];
}
return colorScheme;
}
diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs
index eb93d90dd..7ada30a32 100644
--- a/Terminal.Gui/Text/TextFormatter.cs
+++ b/Terminal.Gui/Text/TextFormatter.cs
@@ -1325,7 +1325,7 @@ namespace Terminal.Gui {
NeedsFormat = false;
return _lines;
}
-
+
if (NeedsFormat) {
var shown_text = _text;
if (FindHotKey (_text, HotKeySpecifier, true, out _hotKeyPos, out var newHotKey)) {
diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs
new file mode 100644
index 000000000..599291001
--- /dev/null
+++ b/Terminal.Gui/View/Adornment/Adornment.cs
@@ -0,0 +1,177 @@
+using System;
+
+namespace Terminal.Gui;
+
+// TODO: v2 - Missing 3D effect - 3D effects will be drawn by a mechanism separate from Adornments
+// TODO: v2 - If a Adornment has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Adornments
+// QUESTION: How does a user navigate out of an Adornment to another Adornment, or back into the Parent's SubViews?
+
+///
+/// Adornments are a special form of that appear outside of the :
+/// , , and . They are defined using the
+/// class, which specifies the thickness of the sides of a rectangle.
+///
+///
+///
+/// There is no prevision for creating additional subclasses of Adornment. It is not abstract to enable unit testing.
+///
+///
+/// Each of , , and can be customized.
+///
+///
+public class Adornment : View {
+ ///
+ public Adornment () { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */ }
+
+ ///
+ /// Constructs a new adornment for the view specified by .
+ ///
+ ///
+ public Adornment (View parent) => Parent = parent;
+
+ Thickness _thickness = Thickness.Empty;
+
+ ///
+ /// The Parent of this Adornment (the View this Adornment surrounds).
+ ///
+ ///
+ /// Adornments are distinguished from typical View classes in that they are not sub-views,
+ /// but have a parent/child relationship with their containing View.
+ ///
+ public View Parent { get; set; }
+
+ ///
+ /// Adornments cannot be used as sub-views (see ); this method always throws an .
+ /// TODO: Are we sure?
+ ///
+ public override View SuperView {
+ get => null;
+ set => throw new NotImplementedException ();
+ }
+
+ ///
+ /// Adornments only render to their 's or Parent's SuperView's LineCanvas,
+ /// so setting this property throws an .
+ ///
+ public override bool SuperViewRendersLineCanvas {
+ get => false; // throw new NotImplementedException ();
+ set => throw new NotImplementedException ();
+ }
+
+ ///
+ /// Defines the rectangle that the will use to draw its content.
+ ///
+ public Thickness Thickness {
+ get => _thickness;
+ set {
+ var prev = _thickness;
+ _thickness = value;
+ if (prev != _thickness) {
+
+ Parent?.LayoutAdornments ();
+ OnThicknessChanged (prev);
+ }
+
+ }
+ }
+
+ ///
+ /// Gets the rectangle that describes the inner area of the Adornment. The Location is always (0,0).
+ ///
+ public override Rect Bounds {
+ get => Thickness?.GetInside (new Rect (Point.Empty, Frame.Size)) ?? new Rect (Point.Empty, Frame.Size);
+ set => throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
+ }
+
+ internal override Adornment CreateAdornment (Type adornmentType)
+ {
+ /* Do nothing - Adornments do not have Adornments */
+ return null;
+ }
+
+ internal override void LayoutAdornments ()
+ {
+ /* Do nothing - Adornments do not have Adornments */
+ }
+
+ ///
+ public override void BoundsToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
+ {
+ // Adornments are *Children* of a View, not SubViews. Thus View.BoundsToScreen will not work.
+ // To get the screen-relative coordinates of a Adornment, we need to know who
+ // the Parent is
+ var parentFrame = Parent?.Frame ?? Frame;
+ rrow = row + parentFrame.Y;
+ rcol = col + parentFrame.X;
+
+ // We now have rcol/rrow in coordinates relative to our View's SuperView. If our View's SuperView has
+ // a SuperView, keep going...
+ Parent?.SuperView?.BoundsToScreen (rcol, rrow, out rcol, out rrow, clipped);
+ }
+
+ ///
+ public override Rect FrameToScreen ()
+ {
+ // Adornments are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work.
+ // To get the screen-relative coordinates of a Adornment, we need to know who
+ // the Parent is
+ var ret = Parent?.Frame ?? Frame;
+ ret.Size = Frame.Size;
+
+ ret.Location = Parent?.FrameToScreen ().Location ?? ret.Location;
+
+ // We now have coordinates relative to our View. If our View's SuperView has
+ // a SuperView, keep going...
+ return ret;
+ }
+
+ ///
+ /// Does nothing for Adornment
+ ///
+ ///
+ public override bool OnDrawAdornments () => false;
+
+ ///
+ /// Does nothing for Adornment
+ ///
+ ///
+ public override bool OnRenderLineCanvas () => false;
+
+ ///
+ /// Redraws the Adornments that comprise the .
+ ///
+ public override void OnDrawContent (Rect contentArea)
+ {
+ if (Thickness == Thickness.Empty) {
+ return;
+ }
+
+ var screenBounds = BoundsToScreen (Frame);
+
+ Attribute normalAttr = GetNormalColor ();
+
+ // This just draws/clears the thickness, not the insides.
+ Driver.SetAttribute (normalAttr);
+ Thickness.Draw (screenBounds, (string)(Data ?? string.Empty));
+
+ if (!string.IsNullOrEmpty (TextFormatter.Text)) {
+ if (TextFormatter != null) {
+ TextFormatter.Size = Frame.Size;
+ TextFormatter.NeedsFormat = true;
+ }
+ }
+
+ TextFormatter?.Draw (screenBounds, normalAttr, normalAttr, Rect.Empty, false);
+ //base.OnDrawContent (contentArea);
+ }
+
+ ///
+ /// Called whenever the property changes.
+ ///
+ public virtual void OnThicknessChanged (Thickness previousThickness) => ThicknessChanged?.Invoke (this, new ThicknessEventArgs { Thickness = Thickness, PreviousThickness = previousThickness });
+
+ ///
+ /// Fired whenever the property changes.
+ ///
+ public event EventHandler ThicknessChanged;
+}
\ No newline at end of file
diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs
new file mode 100644
index 000000000..f37c09d73
--- /dev/null
+++ b/Terminal.Gui/View/Adornment/Border.cs
@@ -0,0 +1,345 @@
+using System;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+///
+/// The Border for a .
+///
+///
+///
+/// Renders a border around the view with the . A border using
+/// will be drawn on the sides of that are greater than zero.
+///
+///
+/// The of will be drawn based on the value of :
+///
+///
+/// If 1:
+///
+/// ┌┤1234├──┐
+/// │ │
+/// └────────┘
+///
+///
+///
+/// If 2:
+///
+/// ┌────┐
+/// ┌┤1234├──┐
+/// │ │
+/// └────────┘
+///
+///
+///
+/// If 3:
+///
+/// ┌────┐
+/// ┌┤1234├──┐
+/// │└────┘ │
+/// │ │
+/// └────────┘
+///
+///
+///
+///
+/// See the class.
+///
+///
+public class Border : Adornment {
+ LineStyle? _lineStyle = null;
+
+ ///
+ public Border () { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */ }
+
+ ///
+ public Border (View parent) : base (parent) { /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */ }
+
+ ///
+ /// The color scheme for the Border. If set to , gets the scheme.
+ /// color scheme.
+ ///
+ public override ColorScheme ColorScheme {
+ get {
+ if (base.ColorScheme != null) {
+ return base.ColorScheme;
+ }
+ return Parent?.ColorScheme;
+ }
+ set {
+ base.ColorScheme = value;
+ Parent?.SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// Sets the style of the border by changing the . This is a helper API for
+ /// setting the to (1,1,1,1) and setting the line style of the
+ /// views that comprise the border. If set to no border will be drawn.
+ ///
+ public LineStyle LineStyle {
+ get {
+ if (_lineStyle.HasValue) {
+ return _lineStyle.Value;
+ }
+ // TODO: Make Border.LineStyle inherit from the SuperView hierarchy
+ // TODO: Right now, Window and FrameView use CM to set BorderStyle, which negates
+ // TODO: all this.
+ return Parent.SuperView?.BorderStyle ?? LineStyle.None;
+ }
+ set => _lineStyle = value;
+ }
+
+ ///
+ public override void OnDrawContent (Rect contentArea)
+ {
+ base.OnDrawContent (contentArea);
+
+ if (Thickness == Thickness.Empty) {
+ return;
+ }
+
+ //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal);
+ var screenBounds = BoundsToScreen (Frame);
+
+ //OnDrawSubviews (bounds);
+
+ // TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
+
+ // The border adornment (and title) are drawn at the outermost edge of border;
+ // For Border
+ // ...thickness extends outward (border/title is always as far in as possible)
+ var borderBounds = new Rect (
+ screenBounds.X + Math.Max (0, Thickness.Left - 1),
+ screenBounds.Y + Math.Max (0, Thickness.Top - 1),
+ Math.Max (0, screenBounds.Width - Math.Max (0, Math.Max (0, Thickness.Left - 1) + Math.Max (0, Thickness.Right - 1))),
+ Math.Max (0, screenBounds.Height - Math.Max (0, Math.Max (0, Thickness.Top - 1) + Math.Max (0, Thickness.Bottom - 1))));
+
+ var topTitleLineY = borderBounds.Y;
+ var titleY = borderBounds.Y;
+ var titleBarsLength = 0; // the little vertical thingies
+ var maxTitleWidth = Math.Min (Parent.Title.GetColumns (), Math.Min (screenBounds.Width - 4, borderBounds.Width - 4));
+ var sideLineLength = borderBounds.Height;
+ var canDrawBorder = borderBounds.Width > 0 && borderBounds.Height > 0;
+
+ if (!string.IsNullOrEmpty (Parent?.Title)) {
+ if (Thickness.Top == 2) {
+ topTitleLineY = borderBounds.Y - 1;
+ titleY = topTitleLineY + 1;
+ titleBarsLength = 2;
+ }
+
+ // ┌────┐
+ //┌┘View└
+ //│
+ if (Thickness.Top == 3) {
+ topTitleLineY = borderBounds.Y - (Thickness.Top - 1);
+ titleY = topTitleLineY + 1;
+ titleBarsLength = 3;
+ sideLineLength++;
+ }
+
+ // ┌────┐
+ //┌┘View└
+ //│
+ if (Thickness.Top > 3) {
+ topTitleLineY = borderBounds.Y - 2;
+ titleY = topTitleLineY + 1;
+ titleBarsLength = 3;
+ sideLineLength++;
+ }
+
+ }
+
+ if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) {
+ var prevAttr = Driver.GetAttribute ();
+ Driver.SetAttribute (Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
+ DrawTitle (new Rect (borderBounds.X, titleY, maxTitleWidth, 1), Parent?.Title);
+ Driver.SetAttribute (prevAttr);
+ }
+
+ if (canDrawBorder && LineStyle != LineStyle.None) {
+ var lc = Parent?.LineCanvas;
+
+ var drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height > 1;
+ var drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
+ var drawBottom = Thickness.Bottom > 0 && Frame.Width > 1;
+ var drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
+
+ var prevAttr = Driver.GetAttribute ();
+ if (ColorScheme != null) {
+ Driver.SetAttribute (GetNormalColor ());
+ } else {
+ Driver.SetAttribute (Parent.GetNormalColor ());
+ }
+
+ if (drawTop) {
+ // ╔╡Title╞═════╗
+ // ╔╡╞═════╗
+ if (borderBounds.Width < 4 || string.IsNullOrEmpty (Parent?.Title)) {
+ // ╔╡╞╗ should be ╔══╗
+ lc.AddLine (new Point (borderBounds.Location.X, titleY), borderBounds.Width, Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+ } else {
+
+ // ┌────┐
+ //┌┘View└
+ //│
+ if (Thickness.Top == 2) {
+ lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+ }
+ // ┌────┐
+ //┌┘View└
+ //│
+ if (borderBounds.Width >= 4 && Thickness.Top > 2) {
+ lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+ lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY + 2), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+ }
+
+ // ╔╡Title╞═════╗
+ // Add a short horiz line for ╔╡
+ lc.AddLine (new Point (borderBounds.Location.X, titleY), 2, Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+ // Add a vert line for ╔╡
+ lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute ());
+ // Add a vert line for ╞
+ lc.AddLine (new Point (borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, topTitleLineY), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute ());
+ // Add the right hand line for ╞═════╗
+ lc.AddLine (new Point (borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, titleY), borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+ }
+ }
+ if (drawLeft) {
+ lc.AddLine (new Point (borderBounds.Location.X, titleY), sideLineLength, Orientation.Vertical, LineStyle, Driver.GetAttribute ());
+ }
+ if (drawBottom) {
+ lc.AddLine (new Point (borderBounds.X, borderBounds.Y + borderBounds.Height - 1), borderBounds.Width, Orientation.Horizontal, LineStyle, Driver.GetAttribute ());
+ }
+ if (drawRight) {
+ lc.AddLine (new Point (borderBounds.X + borderBounds.Width - 1, titleY), sideLineLength, Orientation.Vertical, LineStyle, Driver.GetAttribute ());
+ }
+ Driver.SetAttribute (prevAttr);
+
+ // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
+ if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
+ // Top
+ var hruler = new Ruler { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
+ if (drawTop) {
+ hruler.Draw (new Point (screenBounds.X, screenBounds.Y));
+ }
+
+ // Redraw title
+ if (drawTop && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) {
+ prevAttr = Driver.GetAttribute ();
+ if (ColorScheme != null) {
+ Driver.SetAttribute (HasFocus ? GetHotNormalColor () : GetNormalColor ());
+ } else {
+ Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
+ }
+ DrawTitle (new Rect (borderBounds.X, titleY, Parent.Title.GetColumns (), 1), Parent?.Title);
+ Driver.SetAttribute (prevAttr);
+ }
+
+ //Left
+ var vruler = new Ruler { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
+ if (drawLeft) {
+ vruler.Draw (new Point (screenBounds.X, screenBounds.Y + 1), 1);
+ }
+
+ // Bottom
+ if (drawBottom) {
+ hruler.Draw (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
+ }
+
+ // Right
+ if (drawRight) {
+ vruler.Draw (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
+ }
+
+ }
+ }
+
+ //base.OnDrawContent (contentArea);
+ }
+
+ ///
+ /// Draws the title for a Window-style view.
+ ///
+ /// Screen relative region where the title will be drawn.
+ /// The title.
+ public void DrawTitle (Rect region, string title)
+ {
+ var width = region.Width;
+ if (!string.IsNullOrEmpty (title)) {
+ Driver.Move (region.X + 2, region.Y);
+ //Driver.AddRune (' ');
+ var str = title.EnumerateRunes ().Sum (r => Math.Max (r.GetColumns (), 1)) >= width
+ ? TextFormatter.Format (title, width, false, false) [0] : title;
+ Driver.AddStr (str);
+ }
+ }
+
+ ///
+ /// Draws a frame in the current view, clipped by the boundary of this view
+ ///
+ /// View-relative region for the frame to be drawn.
+ /// If set to it clear the region.
+ [Obsolete ("This method is obsolete in v2. Use use LineCanvas or Frame instead.", false)]
+ public void DrawFrame (Rect region, bool clear)
+ {
+ var savedClip = ClipToBounds ();
+ var screenBounds = BoundsToScreen (region);
+
+ if (clear) {
+ Driver.FillRect (region);
+ }
+
+ var lc = new LineCanvas ();
+ var drawTop = region.Width > 1 && region.Height > 1;
+ var drawLeft = region.Width > 1 && region.Height > 1;
+ var drawBottom = region.Width > 1 && region.Height > 1;
+ var drawRight = region.Width > 1 && region.Height > 1;
+
+ if (drawTop) {
+ lc.AddLine (screenBounds.Location, screenBounds.Width, Orientation.Horizontal, LineStyle);
+ }
+ if (drawLeft) {
+ lc.AddLine (screenBounds.Location, screenBounds.Height, Orientation.Vertical, LineStyle);
+ }
+ if (drawBottom) {
+ lc.AddLine (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1), screenBounds.Width, Orientation.Horizontal, LineStyle);
+ }
+ if (drawRight) {
+ lc.AddLine (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y), screenBounds.Height, Orientation.Vertical, LineStyle);
+ }
+ foreach (var p in lc.GetMap ()) {
+ Driver.Move (p.Key.X, p.Key.Y);
+ Driver.AddRune (p.Value);
+ }
+ lc.Clear ();
+
+ // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
+ if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
+ // Top
+ var hruler = new Ruler { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
+ if (drawTop) {
+ hruler.Draw (new Point (screenBounds.X, screenBounds.Y));
+ }
+
+ //Left
+ var vruler = new Ruler { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
+ if (drawLeft) {
+ vruler.Draw (new Point (screenBounds.X, screenBounds.Y + 1), 1);
+ }
+
+ // Bottom
+ if (drawBottom) {
+ hruler.Draw (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
+ }
+
+ // Right
+ if (drawRight) {
+ vruler.Draw (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
+ }
+ }
+
+ Driver.Clip = savedClip;
+ }
+}
diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs
new file mode 100644
index 000000000..82737ec16
--- /dev/null
+++ b/Terminal.Gui/View/Adornment/Margin.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+///
+/// The Margin for a .
+///
+///
+///
+/// See the class.
+///
+///
+public class Margin : Adornment {
+ ///
+ public Margin () { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */ }
+
+ ///
+ public Margin (View parent) : base (parent) { /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */ }
+
+ ///
+ /// The color scheme for the Margin. If set to , gets the 's scheme.
+ /// color scheme.
+ ///
+ public override ColorScheme ColorScheme {
+ get {
+ if (base.ColorScheme != null) {
+ return base.ColorScheme;
+ }
+ return Parent?.SuperView?.ColorScheme ?? Colors.ColorSchemes ["TopLevel"];
+ }
+ set {
+ base.ColorScheme = value;
+ Parent?.SetNeedsDisplay ();
+ }
+ }
+}
diff --git a/Terminal.Gui/View/Adornment/Padding.cs b/Terminal.Gui/View/Adornment/Padding.cs
new file mode 100644
index 000000000..2a97074af
--- /dev/null
+++ b/Terminal.Gui/View/Adornment/Padding.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+///
+/// The Padding for a .
+///
+///
+///
+/// See the class.
+///
+///
+public class Padding : Adornment {
+ ///
+ public Padding () { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */ }
+
+ ///
+ public Padding (View parent) : base (parent) { /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */ }
+
+ ///
+ /// The color scheme for the Padding. If set to , gets the scheme.
+ /// color scheme.
+ ///
+ public override ColorScheme ColorScheme {
+ get {
+ if (base.ColorScheme != null) {
+ return base.ColorScheme;
+ }
+ return Parent?.ColorScheme;
+ }
+ set {
+ base.ColorScheme = value;
+ Parent?.SetNeedsDisplay ();
+ }
+ }
+}
diff --git a/Terminal.Gui/View/Frame.cs b/Terminal.Gui/View/Frame.cs
deleted file mode 100644
index 484ce3cd1..000000000
--- a/Terminal.Gui/View/Frame.cs
+++ /dev/null
@@ -1,446 +0,0 @@
-using System.Text;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Xml.Linq;
-using static Terminal.Gui.TileView;
-
-namespace Terminal.Gui {
-
- // TODO: v2 - Missing 3D effect - 3D effects will be drawn by a mechanism separate from Frames
- // TODO: v2 - If a Frame has focus, navigation keys (e.g Command.NextView) should cycle through SubViews of the Frame
- // QUESTION: How does a user navigate out of a Frame to another Frame, or back into the Parent's SubViews?
-
- ///
- /// Frames are a special form of that act as adornments; they appear outside of the
- /// enabling borders, menus, etc...
- ///
- public class Frame : View {
- private Thickness _thickness = Thickness.Empty;
-
- internal override void CreateFrames () { /* Do nothing - Frames do not have Frames */ }
- internal override void LayoutFrames () { /* Do nothing - Frames do not have Frames */ }
-
- ///
- /// The Parent of this Frame (the View this Frame surrounds).
- ///
- public View Parent { get; set; }
-
- ///
- /// Frames cannot be used as sub-views, so this method always throws an .
- /// TODO: Are we sure?
- ///
- public override View SuperView {
- get {
- return null;
- }
- set {
- throw new NotImplementedException ();
- }
- }
-
- ///
- public override void BoundsToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
- {
- // Frames are *Children* of a View, not SubViews. Thus View.BoundsToScreen will not work.
- // To get the screen-relative coordinates of a Frame, we need to know who
- // the Parent is
- var parentFrame = Parent?.Frame ?? Frame;
- rrow = row + parentFrame.Y;
- rcol = col + parentFrame.X;
-
- // We now have rcol/rrow in coordinates relative to our View's SuperView. If our View's SuperView has
- // a SuperView, keep going...
- Parent?.SuperView?.BoundsToScreen (rcol, rrow, out rcol, out rrow, clipped);
- }
-
- ///
- public override Rect FrameToScreen ()
- {
- // Frames are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work.
- // To get the screen-relative coordinates of a Frame, we need to know who
- // the Parent is
- var ret = Parent?.Frame ?? Frame;
- ret.Size = Frame.Size;
-
- ret.Location = Parent?.FrameToScreen ().Location ?? ret.Location;
-
- // We now have coordinates relative to our View. If our View's SuperView has
- // a SuperView, keep going...
- return ret;
- }
-
- ///
- /// Does nothing for Frame
- ///
- ///
- public override bool OnDrawFrames () => false;
-
- ///
- /// Does nothing for Frame
- ///
- ///
- public override bool OnRenderLineCanvas () => false;
-
- ///
- /// Frames only render to their Parent or Parent's SuperView's LineCanvas,
- /// so this always throws an .
- ///
- public override bool SuperViewRendersLineCanvas {
- get {
- return false;// throw new NotImplementedException ();
- }
- set {
- throw new NotImplementedException ();
- }
- }
-
- ///
- ///
- ///
- ///
- public virtual void OnDrawSubViews (Rect clipRect)
- {
- // TODO: Enable subviews of Frames (adornments).
- // if (Subviews == null) {
- // return;
- // }
-
- // foreach (var view in Subviews) {
- // // BUGBUG: v2 - shouldn't this be !view.LayoutNeeded? Why draw if layout is going to happen and we'll just draw again?
- // if (view.LayoutNeeded) {
- // view.LayoutSubviews ();
- // }
- // if ((view.Visible && !view.NeedDisplay.IsEmpty && view.Frame.Width > 0 && view.Frame.Height > 0) || view.ChildNeedsDisplay) {
- // view.Redraw (view.Bounds);
-
- // view.NeedDisplay = Rect.Empty;
- // // BUGBUG - v2 why does this need to be set to false?
- // // Shouldn't it be set when the subviews draw?
- // view.ChildNeedsDisplay = false;
- // }
- // }
-
- }
-
- ///
- /// Redraws the Frames that comprise the .
- ///
- public override void OnDrawContent (Rect contentArea)
- {
- if (Thickness == Thickness.Empty) {
- return;
- }
-
- if (ColorScheme != null) {
- Driver.SetAttribute (GetNormalColor ());
- } else {
- if (Id == "Padding") {
- Driver.SetAttribute (new Attribute (Parent.ColorScheme.HotNormal.Background, Parent.ColorScheme.HotNormal.Foreground));
- } else {
- Driver.SetAttribute (Parent.GetNormalColor ());
- }
- }
-
- //Driver.SetAttribute (Colors.Error.Normal);
- var screenBounds = BoundsToScreen (Frame);
-
- // This just draws/clears the thickness, not the insides.
- Thickness.Draw (screenBounds, (string)(Data != null ? Data : string.Empty));
-
- //OnDrawSubviews (bounds);
-
- // TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title)
-
- // The border frame (and title) are drawn at the outermost edge of border;
- // For Border
- // ...thickness extends outward (border/title is always as far in as possible)
- var borderBounds = new Rect (
- screenBounds.X + Math.Max (0, Thickness.Left - 1),
- screenBounds.Y + Math.Max (0, Thickness.Top - 1),
- Math.Max (0, screenBounds.Width - Math.Max (0, Math.Max (0, Thickness.Left - 1) + Math.Max (0, Thickness.Right - 1))),
- Math.Max (0, screenBounds.Height - Math.Max (0, Math.Max (0, Thickness.Top - 1) + Math.Max (0, Thickness.Bottom - 1))));
-
- var topTitleLineY = borderBounds.Y;
- var titleY = borderBounds.Y;
- var titleBarsLength = 0; // the little vertical thingies
- var maxTitleWidth = Math.Min (Parent.Title.GetColumns (), Math.Min (screenBounds.Width - 4, borderBounds.Width - 4));
- var sideLineLength = borderBounds.Height;
- var canDrawBorder = borderBounds.Width > 0 && borderBounds.Height > 0;
-
- if (!string.IsNullOrEmpty (Parent?.Title)) {
- if (Thickness.Top == 2) {
- topTitleLineY = borderBounds.Y - 1;
- titleY = topTitleLineY + 1;
- titleBarsLength = 2;
- }
-
- // ┌────┐
- //┌┘View└
- //│
- if (Thickness.Top == 3) {
- topTitleLineY = borderBounds.Y - (Thickness.Top - 1);
- titleY = topTitleLineY + 1;
- titleBarsLength = 3;
- sideLineLength++;
- }
-
- // ┌────┐
- //┌┘View└
- //│
- if (Thickness.Top > 3) {
- topTitleLineY = borderBounds.Y - 2;
- titleY = topTitleLineY + 1;
- titleBarsLength = 3;
- sideLineLength++;
- }
-
- }
-
- if (Id == "Border" && canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) {
- var prevAttr = Driver.GetAttribute ();
- if (ColorScheme != null) {
- Driver.SetAttribute (HasFocus ? GetHotNormalColor () : GetNormalColor ());
- } else {
- Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
- }
- DrawTitle (new Rect (borderBounds.X, titleY, maxTitleWidth, 1), Parent?.Title);
- Driver.SetAttribute (prevAttr);
- }
-
- if (Id == "Border" && canDrawBorder && BorderStyle != LineStyle.None) {
- LineCanvas lc = Parent?.LineCanvas;
-
- var drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height > 1;
- var drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
- var drawBottom = Thickness.Bottom > 0 && Frame.Width > 1;
- var drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
-
- var prevAttr = Driver.GetAttribute ();
- if (ColorScheme != null) {
- Driver.SetAttribute (GetNormalColor ());
- } else {
- Driver.SetAttribute (Parent.GetNormalColor ());
- }
-
- if (drawTop) {
- // ╔╡Title╞═════╗
- // ╔╡╞═════╗
- if (borderBounds.Width < 4 || string.IsNullOrEmpty (Parent?.Title)) {
- // ╔╡╞╗ should be ╔══╗
- lc.AddLine (new Point (borderBounds.Location.X, titleY), borderBounds.Width, Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
- } else {
-
- // ┌────┐
- //┌┘View└
- //│
- if (Thickness.Top == 2) {
- lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
- }
- // ┌────┐
- //┌┘View└
- //│
- if (borderBounds.Width >= 4 && Thickness.Top > 2) {
- lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
- lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY + 2), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
- }
-
- // ╔╡Title╞═════╗
- // Add a short horiz line for ╔╡
- lc.AddLine (new Point (borderBounds.Location.X, titleY), 2, Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
- // Add a vert line for ╔╡
- lc.AddLine (new Point (borderBounds.X + 1, topTitleLineY), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute ());
- // Add a vert line for ╞
- lc.AddLine (new Point (borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, topTitleLineY), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute ());
- // Add the right hand line for ╞═════╗
- lc.AddLine (new Point (borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, titleY), borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
- }
- }
- if (drawLeft) {
- lc.AddLine (new Point (borderBounds.Location.X, titleY), sideLineLength, Orientation.Vertical, BorderStyle, Driver.GetAttribute ());
- }
- if (drawBottom) {
- lc.AddLine (new Point (borderBounds.X, borderBounds.Y + borderBounds.Height - 1), borderBounds.Width, Orientation.Horizontal, BorderStyle, Driver.GetAttribute ());
- }
- if (drawRight) {
- lc.AddLine (new Point (borderBounds.X + borderBounds.Width - 1, titleY), sideLineLength, Orientation.Vertical, BorderStyle, Driver.GetAttribute ());
- }
- Driver.SetAttribute (prevAttr);
-
- // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
- if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
- // Top
- var hruler = new Ruler () { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
- if (drawTop) {
- hruler.Draw (new Point (screenBounds.X, screenBounds.Y));
- }
-
- // Redraw title
- if (drawTop && Id == "Border" && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) {
- prevAttr = Driver.GetAttribute ();
- if (ColorScheme != null) {
- Driver.SetAttribute (HasFocus ? GetHotNormalColor () : GetNormalColor ());
- } else {
- Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
- }
- DrawTitle (new Rect (borderBounds.X, titleY, Parent.Title.GetColumns (), 1), Parent?.Title);
- Driver.SetAttribute (prevAttr);
- }
-
- //Left
- var vruler = new Ruler () { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
- if (drawLeft) {
- vruler.Draw (new Point (screenBounds.X, screenBounds.Y + 1), 1);
- }
-
- // Bottom
- if (drawBottom) {
- hruler.Draw (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
- }
-
- // Right
- if (drawRight) {
- vruler.Draw (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
- }
-
- }
- }
-
- ClearNeedsDisplay ();
- }
-
- // TODO: v2 - Frame.BorderStyle is temporary - Eventually the border will be drawn by a "BorderView" that is a subview of the Frame.
- ///
- ///
- ///
- public new LineStyle BorderStyle { get; set; } = LineStyle.None;
-
- ///
- /// Defines the rectangle that the will use to draw its content.
- ///
- public Thickness Thickness {
- get { return _thickness; }
- set {
- var prev = _thickness;
- _thickness = value;
- if (prev != _thickness) {
-
- Parent?.LayoutFrames ();
- OnThicknessChanged (prev);
- }
-
- }
- }
-
- ///
- /// Called whenever the property changes.
- ///
- public virtual void OnThicknessChanged (Thickness previousThickness)
- {
- ThicknessChanged?.Invoke (this, new ThicknessEventArgs () { Thickness = Thickness, PreviousThickness = previousThickness });
- }
-
- ///
- /// Fired whenever the property changes.
- ///
- public event EventHandler ThicknessChanged;
-
- ///
- /// Gets the rectangle that describes the inner area of the frame. The Location is always (0,0).
- ///
- public override Rect Bounds {
- get {
- return Thickness?.GetInside (new Rect (Point.Empty, Frame.Size)) ?? new Rect (Point.Empty, Frame.Size);
- }
- set {
- throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness.");
- }
- }
-
- ///
- /// Draws the title for a Window-style view.
- ///
- /// Screen relative region where the title will be drawn.
- /// The title.
- public void DrawTitle (Rect region, string title)
- {
- var width = region.Width;
- if (!string.IsNullOrEmpty (title)) {
- Driver.Move (region.X + 2, region.Y);
- //Driver.AddRune (' ');
- var str = title.EnumerateRunes ().Sum (r => Math.Max (r.GetColumns (), 1)) >= width
- ? TextFormatter.Format (title, width, false, false) [0] : title;
- Driver.AddStr (str);
- }
- }
-
- ///
- /// Draws a frame in the current view, clipped by the boundary of this view
- ///
- /// View-relative region for the frame to be drawn.
- /// If set to it clear the region.
- [ObsoleteAttribute ("This method is obsolete in v2. Use use LineCanvas or Frame instead.", false)]
- public void DrawFrame (Rect region, bool clear)
- {
- var savedClip = ClipToBounds ();
- var screenBounds = BoundsToScreen (region);
-
- if (clear) {
- Driver.FillRect (region);
- }
-
- var lc = new LineCanvas ();
- var drawTop = region.Width > 1 && region.Height > 1;
- var drawLeft = region.Width > 1 && region.Height > 1;
- var drawBottom = region.Width > 1 && region.Height > 1;
- var drawRight = region.Width > 1 && region.Height > 1;
-
- if (drawTop) {
- lc.AddLine (screenBounds.Location, screenBounds.Width, Orientation.Horizontal, BorderStyle);
- }
- if (drawLeft) {
- lc.AddLine (screenBounds.Location, screenBounds.Height, Orientation.Vertical, BorderStyle);
- }
- if (drawBottom) {
- lc.AddLine (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1), screenBounds.Width, Orientation.Horizontal, BorderStyle);
- }
- if (drawRight) {
- lc.AddLine (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y), screenBounds.Height, Orientation.Vertical, BorderStyle);
- }
- foreach (var p in lc.GetMap ()) {
- Driver.Move (p.Key.X, p.Key.Y);
- Driver.AddRune (p.Value);
- }
- lc.Clear ();
-
- // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler
- if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
- // Top
- var hruler = new Ruler () { Length = screenBounds.Width, Orientation = Orientation.Horizontal };
- if (drawTop) {
- hruler.Draw (new Point (screenBounds.X, screenBounds.Y));
- }
-
- //Left
- var vruler = new Ruler () { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical };
- if (drawLeft) {
- vruler.Draw (new Point (screenBounds.X, screenBounds.Y + 1), 1);
- }
-
- // Bottom
- if (drawBottom) {
- hruler.Draw (new Point (screenBounds.X, screenBounds.Y + screenBounds.Height - 1));
- }
-
- // Right
- if (drawRight) {
- vruler.Draw (new Point (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1);
- }
- }
-
- Driver.Clip = savedClip;
- }
-
- }
-}
diff --git a/Terminal.Gui/View/Layout/PosDim.cs b/Terminal.Gui/View/Layout/PosDim.cs
index ca59c83db..b593996ad 100644
--- a/Terminal.Gui/View/Layout/PosDim.cs
+++ b/Terminal.Gui/View/Layout/PosDim.cs
@@ -4,9 +4,11 @@ namespace Terminal.Gui;
///
/// Describes the position of a which can be an absolute value, a percentage, centered, or
+/// Describes the position of a which can be an absolute value, a percentage, centered, or
/// relative to the ending dimension. Integer values are implicitly convertible to
/// an absolute . These objects are created using the static methods Percent,
/// AnchorEnd, and Center. The objects can be combined with the addition and
+/// AnchorEnd, and Center. The objects can be combined with the addition and
/// subtraction operators.
///
///
@@ -132,6 +134,128 @@ namespace Terminal.Gui;
///
///
///
+///
+/// Use the objects on the X or Y properties of a view to control the position.
+///
+///
+/// These can be used to set the absolute position, when merely assigning an
+/// integer value (via the implicit integer to conversion), and they can be combined
+/// to produce more useful layouts, like: Pos.Center - 3, which would shift the position
+/// of the 3 characters to the left after centering for example.
+///
+///
+/// Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View).
+/// The X(View) and Y(View) are
+/// aliases to Left(View) and Top(View) respectively.
+///
+///
+///
+///
+/// Pos Object
+/// Description
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that computes the position by executing the provided
+/// function. The function will be called every time the position is needed.
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that is a percentage of the width or height of the
+/// SuperView.
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that is anchored to the end (right side or bottom)
+/// of the dimension,
+/// useful to flush the layout from the right or bottom.
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that can be used to center the .
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that is an absolute position based on the specified
+/// integer value.
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that tracks the Left (X) position of the specified
+/// .
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that tracks the Left (X) position of the specified
+/// .
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that tracks the Top (Y) position of the specified
+/// .
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that tracks the Top (Y) position of the specified
+/// .
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that tracks the Right (X+Width) coordinate of the
+/// specified .
+///
+///
+/// -
+///
+///
+///
+///
+/// Creates a object that tracks the Bottom (Y+Height) coordinate of the
+/// specified
+///
+///
+///
+///
+///
///
public class Pos {
internal virtual int Anchor (int width) => 0;
diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs
index b7d6d49d7..3c72546c1 100644
--- a/Terminal.Gui/View/Layout/ViewLayout.cs
+++ b/Terminal.Gui/View/Layout/ViewLayout.cs
@@ -1,5 +1,4 @@
using System;
-using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
@@ -22,6 +21,20 @@ namespace Terminal.Gui;
///
/// objects are relative to the and are computed at layout time.
///
+///
+/// Indicates the LayoutStyle for the .
+///
+///
+/// If Absolute, the , , , and
+///
+/// objects are all absolute values and are not relative. The position and size of the view is described by
+/// .
+///
+///
+/// If Computed, one or more of the , , , or
+///
+/// objects are relative to the and are computed at layout time.
+///
///
public enum LayoutStyle {
///
@@ -87,7 +100,7 @@ public partial class View {
// TODO: Figure out if the below can be optimized.
if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) {
- LayoutFrames ();
+ LayoutAdornments ();
SetTextFormatterSize ();
SetNeedsLayout ();
SetNeedsDisplay ();
@@ -101,21 +114,19 @@ public partial class View {
///
///
///
- /// The frames (, , and ) are not part of the View's
- /// content
- /// and are not clipped by the View's Clip Area.
+ /// The adornments (, , and ) are not part of the View's
+ /// content and are not clipped by the View's Clip Area.
///
///
- /// Changing the size of a frame (, , or )
- /// will change the size of the and trigger to update the layout
- /// of the
- /// and its .
+ /// Changing the size of an adornment (, , or )
+ /// will change the size of and trigger to update the layout
+ /// of the and its .
///
///
- public Frame Margin { get; private set; }
+ public Margin Margin { get; private set; }
///
- /// The frame (specified as a ) inside of the view that offsets the from the
+ /// The adornment (specified as a ) inside of the view that offsets the from the
/// .
/// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title.
/// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and
@@ -126,9 +137,8 @@ public partial class View {
/// provides a simple helper for turning a simple border frame on or off.
///
///
- /// The frames (, , and ) are not part of the View's
- /// content
- /// and are not clipped by the View's Clip Area.
+ /// The adornments (, , and ) are not part of the View's
+ /// content and are not clipped by the View's Clip Area.
///
///
/// Changing the size of a frame (, , or )
@@ -137,7 +147,7 @@ public partial class View {
/// and its .
///
///
- public Frame Border { get; private set; }
+ public Border Border { get; private set; }
///
/// Gets or sets whether the view has a one row/col thick border.
@@ -146,12 +156,12 @@ public partial class View {
///
/// This is a helper for manipulating the view's . Setting this property to any value other
/// than
- /// is equivalent to setting 's
+ /// is equivalent to setting 's
/// to `1` and to the value.
///
///
/// Setting this property to is equivalent to setting 's
- ///
+ ///
/// to `0` and to .
///
///
@@ -159,18 +169,15 @@ public partial class View {
///
///
public LineStyle BorderStyle {
- get => Border?.BorderStyle ?? LineStyle.None;
+ get => Border.LineStyle;
set {
- if (Border == null) {
- throw new InvalidOperationException ("Border is null; this is likely a bug.");
- }
if (value != LineStyle.None) {
Border.Thickness = new Thickness (1);
} else {
Border.Thickness = new Thickness (0);
}
- Border.BorderStyle = value;
- LayoutFrames ();
+ Border.LineStyle = value;
+ LayoutAdornments ();
SetNeedsLayout ();
}
}
@@ -181,9 +188,8 @@ public partial class View {
///
///
///
- /// The frames (, , and ) are not part of the View's
- /// content
- /// and are not clipped by the View's Clip Area.
+ /// The adornments (, , and ) are not part of the View's
+ /// content and are not clipped by the View's Clip Area.
///
///
/// Changing the size of a frame (, , or )
@@ -192,24 +198,73 @@ public partial class View {
/// and its .
///
///
- public Frame Padding { get; private set; }
+ public Padding Padding { get; private set; }
///
///
- /// Gets the LayoutStyle for the .
- ///
- ///
- /// If Absolute, the , , , and
- ///
- /// objects are all absolute values and are not relative. The position and size of the view is described by
- /// .
- ///
- ///
- /// If Computed, one or more of the , , , or
- ///
- /// objects are relative to the and are computed at layout time.
+ /// Gets the thickness describing the sum of the Adornments' thicknesses.
///
///
+ /// A thickness that describes the sum of the Adornments' thicknesses.
+ public Thickness GetAdornmentsThickness ()
+ {
+ int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
+ int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
+ int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
+ int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
+ return new Thickness (left, top, right, bottom);
+ }
+
+ ///
+ /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
+ /// , and .
+ ///
+ public Point GetBoundsOffset () => new (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
+
+ ///
+ /// This internal method is overridden by Adornment to do nothing to prevent recursion during View construction.
+ /// And, because Adornments don't have Adornments. It's internal to support unit tests.
+ ///
+ ///
+ ///
+ ///
+ internal virtual Adornment CreateAdornment (Type adornmentType)
+ {
+ void ThicknessChangedHandler (object sender, EventArgs e)
+ {
+ if (IsInitialized) {
+ LayoutAdornments ();
+ }
+ SetNeedsLayout ();
+ SetNeedsDisplay ();
+ }
+
+ Adornment adornment;
+
+ adornment = Activator.CreateInstance (adornmentType, this) as Adornment;
+ adornment.ThicknessChanged += ThicknessChangedHandler;
+
+ return adornment;
+ }
+
+ ///
+ /// Controls how the View's is computed during . If the style is set to
+ /// , LayoutSubviews does not change the .
+ /// If the style is the is updated using
+ /// the , , , and properties.
+ ///
+ ///
+ ///
+ /// Setting this property to will cause to determine the
+ /// size and position of the view. and will be set to using .
+ ///
+ ///
+ /// Setting this property to will cause the view to use the method to
+ /// size and position of the view. If either of the and properties are `null` they will be set to using
+ /// the current value of .
+ /// If either of the and properties are `null` they will be set to using .
+ ///
+ ///
/// The layout style.
public LayoutStyle LayoutStyle {
get {
@@ -253,8 +308,13 @@ public partial class View {
Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}");
}
#endif // DEBUG
- var frameRelativeBounds = FrameGetInsideBounds ();
- return new Rect (default, frameRelativeBounds.Size);
+ // BUGBUG: I think there's a bug here. This should be && not ||
+ if (Margin == null || Border == null || Padding == null) {
+ return new Rect (default, Frame.Size);
+ }
+ var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
+ var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
+ return new Rect (Point.Empty, new Size (width, height));
}
set {
// TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is
@@ -380,6 +440,7 @@ public partial class View {
///
/// Gets or sets the height dimension of the view.
+ /// Gets or sets whether validation of and occurs.
///
/// The object representing the height of the view (the number of rows).
///
@@ -452,7 +513,7 @@ public partial class View {
break;
case Pos pos and Pos.PosCombine:
// Recursively check for not Absolute or not View
- ThrowInvalid (view, (pos as Pos.PosCombine)._left, name);
+ ThrowInvalid (view, (pos as Pos.PosCombine)._left, name);
ThrowInvalid (view, (pos as Pos.PosCombine)._right, name);
break;
@@ -461,7 +522,7 @@ public partial class View {
break;
case Dim dim and Dim.DimCombine:
// Recursively check for not Absolute or not View
- ThrowInvalid (view, (dim as Dim.DimCombine)._left, name);
+ ThrowInvalid (view, (dim as Dim.DimCombine)._left, name);
ThrowInvalid (view, (dim as Dim.DimCombine)._right, name);
break;
}
@@ -476,15 +537,15 @@ public partial class View {
foreach (var view in Subviews) {
if (Width is Dim.DimAuto { _min: null }) {
ThrowInvalid (view, view.Width, nameof (view.Width));
- ThrowInvalid (view, view.X, nameof (view.X));
+ ThrowInvalid (view, view.X, nameof (view.X));
}
if (Height is Dim.DimAuto { _min: null }) {
ThrowInvalid (view, view.Height, nameof (view.Height));
- ThrowInvalid (view, view.Y, nameof (view.Y));
+ ThrowInvalid (view, view.Y, nameof (view.Y));
}
}
}
-
+
internal bool LayoutNeeded { get; private set; } = true;
///
@@ -527,76 +588,7 @@ public partial class View {
///
public event EventHandler Initialized;
- ///
- /// Helper to get the total thickness of the , , and .
- ///
- /// A thickness that describes the sum of the Frames' thicknesses.
- public Thickness GetFramesThickness ()
- {
- var left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
- var top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
- var right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
- var bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
- return new Thickness (left, top, right, bottom);
- }
- ///
- /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
- /// , and .
- ///
- public Point GetBoundsOffset () => new (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
-
- ///
- /// Creates the view's objects. This internal method is overridden by Frame to do nothing
- /// to prevent recursion during View construction.
- ///
- internal virtual void CreateFrames ()
- {
- void ThicknessChangedHandler (object sender, EventArgs e)
- {
- if (IsInitialized) {
- LayoutFrames ();
- }
- SetNeedsLayout ();
- SetNeedsDisplay ();
- }
-
- if (Margin != null) {
- Margin.ThicknessChanged -= ThicknessChangedHandler;
- Margin.Dispose ();
- }
- Margin = new Frame { Id = "Margin", Thickness = new Thickness (0) };
- Margin.ThicknessChanged += ThicknessChangedHandler;
- Margin.Parent = this;
-
- if (Border != null) {
- Border.ThicknessChanged -= ThicknessChangedHandler;
- Border.Dispose ();
- }
- Border = new Frame { Id = "Border", Thickness = new Thickness (0) };
- Border.ThicknessChanged += ThicknessChangedHandler;
- Border.Parent = this;
-
- // TODO: Create View.AddAdornment
-
- if (Padding != null) {
- Padding.ThicknessChanged -= ThicknessChangedHandler;
- Padding.Dispose ();
- }
- Padding = new Frame { Id = "Padding", Thickness = new Thickness (0) };
- Padding.ThicknessChanged += ThicknessChangedHandler;
- Padding.Parent = this;
- }
-
- Rect FrameGetInsideBounds ()
- {
- if (Margin == null || Border == null || Padding == null) {
- return new Rect (default, Frame.Size);
- }
- var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
- var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
- return new Rect (Point.Empty, new Size (width, height));
- }
// Diagnostics to highlight when X or Y is read before the view has been initialized
Pos VerifyIsInitialized (Pos pos, string member)
@@ -632,33 +624,24 @@ public partial class View {
///
internal void OnResizeNeeded ()
{
- SuperView?.CheckDimAuto ();
-
// TODO: Identify a real-world use-case where this API should be virtual.
// TODO: Until then leave it `internal` and non-virtual
// First try SuperView.Bounds, then Application.Top, then Driver.Bounds.
// Finally, if none of those are valid, use int.MaxValue (for Unit tests).
- var relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds :
+ var relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds :
Application.Top != null && Application.Top.IsInitialized ? Application.Top.Bounds :
Application.Driver?.Bounds ??
new Rect (0, 0, int.MaxValue, int.MaxValue);
SetRelativeLayout (relativeBounds);
-
+
// TODO: Determine what, if any of the below is actually needed here.
if (IsInitialized) {
SetFrameToFitText ();
- LayoutFrames ();
+ LayoutAdornments ();
SetTextFormatterSize ();
SetNeedsLayout ();
SetNeedsDisplay ();
}
-
- //if (IsInitialized && SuperView != null && LayoutStyle == LayoutStyle.Computed && (SuperView?.Height is Dim.DimAuto || SuperView?.Width is Dim.DimAuto)) {
- // // DimAuto is in play, force a layout.
- // // BUGBUG: This can cause LayoutSubviews to be called recursively resulting in a deadlock.
- // // SetNeedsLayout should be sufficient, but it's not.
- // SuperView.LayoutSubviews ();
- //}
}
///
@@ -792,6 +775,8 @@ public partial class View {
Debug.Assert (_width != null);
Debug.Assert (_height != null);
+ CheckDimAuto ();
+
int newX, newW, newY, newH;
var autosize = Size.Empty;
@@ -824,7 +809,7 @@ public partial class View {
case Dim.DimCombine combine:
// TODO: Move combine logic into DimCombine?
- var leftNewDim = GetNewDimension (combine._left, location, dimension, autosize);
+ var leftNewDim = GetNewDimension (combine._left, location, dimension, autosize);
var rightNewDim = GetNewDimension (combine._right, location, dimension, autosize);
if (combine._add) {
newDimension = leftNewDim + rightNewDim;
@@ -840,7 +825,7 @@ public partial class View {
break;
case Dim.DimAuto auto:
- var thickness = GetFramesThickness ();
+ var thickness = GetAdornmentsThickness ();
//newDimension = GetNewDimension (auto._min, location, dimension, autosize);
if (width) {
var furthestRight = Subviews.Count == 0 ? 0 : Subviews.Where (v => v.X is not Pos.PosAnchorEnd).Max (v => v.Frame.X + v.Frame.Width);
@@ -883,6 +868,7 @@ public partial class View {
break;
case Pos.PosCombine combine:
+ // TODO: Move combine logic into PosCombine?
// TODO: Move combine logic into PosCombine?
int left, right;
(left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension);
@@ -936,15 +922,14 @@ public partial class View {
if (IsInitialized) {
// TODO: Figure out what really is needed here. All unit tests (except AutoSize) pass as-is
- //LayoutFrames ();
SetTextFormatterSize ();
SetNeedsLayout ();
- //SetNeedsDisplay ();
}
// BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
if (!SetFrameToFitText ()) {
SetTextFormatterSize ();
+ SetTextFormatterSize ();
}
}
}
@@ -1099,12 +1084,12 @@ public partial class View {
} // TopologicalSort
///
- /// Overriden by to do nothing, as the does not have frames.
+ /// Overriden by to do nothing, as the does not have adornments.
///
- internal virtual void LayoutFrames ()
+ internal virtual void LayoutAdornments ()
{
if (Margin == null) {
- return; // CreateFrames() has not been called yet
+ return; // CreateAdornments () has not been called yet
}
if (Margin.Frame.Size != Frame.Size) {
@@ -1165,7 +1150,7 @@ public partial class View {
CheckDimAuto ();
- LayoutFrames ();
+ LayoutAdornments ();
var oldBounds = Bounds;
OnLayoutStarted (new LayoutEventArgs { OldBounds = oldBounds });
diff --git a/Terminal.Gui/View/SuperViewChangedEventArgs.cs b/Terminal.Gui/View/SuperViewChangedEventArgs.cs
index 13f710491..9a7b43728 100644
--- a/Terminal.Gui/View/SuperViewChangedEventArgs.cs
+++ b/Terminal.Gui/View/SuperViewChangedEventArgs.cs
@@ -1,6 +1,6 @@
using System;
-namespace Terminal.Gui;
+namespace Terminal.Gui;
///
/// Args for events where the of a is changed
diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs
index df3abf059..54e8d0c73 100644
--- a/Terminal.Gui/View/View.cs
+++ b/Terminal.Gui/View/View.cs
@@ -68,7 +68,7 @@ namespace Terminal.Gui;
/// points. The Width and Height properties are of type
/// and can use absolute position,
/// percentages, and anchors. These are useful as they will take
-/// care of repositioning views when view's frames are resized or
+/// care of repositioning views when view's adornments are resized or
/// if the terminal size changes.
///
///
@@ -88,7 +88,7 @@ namespace Terminal.Gui;
/// The method is invoked when the size or layout of a view has
/// changed. The default processing system will keep the size and dimensions
/// for views that use the , and will recompute the
-/// frames for the vies that use .
+/// Adornments for the views that use .
///
///
/// Views have a property that defines the default colors that subviews
@@ -430,9 +430,9 @@ public partial class View : Responder, ISupportInitializeNotification {
/// control the size and location of the view, changing it to .
///
///
- /// Location.
+ /// Location.
/// text to initialize the property with.
- public View (Rect rect, string text) => SetInitialProperties (text, rect, LayoutStyle.Absolute);
+ public View (Rect frame, string text) => SetInitialProperties (text, frame, LayoutStyle.Absolute);
///
@@ -493,7 +493,9 @@ public partial class View : Responder, ISupportInitializeNotification {
AddCommands ();
- CreateFrames ();
+ Margin = CreateAdornment (typeof (Margin)) as Margin;
+ Border = CreateAdornment (typeof (Border)) as Border;
+ Padding = CreateAdornment (typeof (Padding)) as Padding;
}
///
@@ -534,12 +536,6 @@ public partial class View : Responder, ISupportInitializeNotification {
if (!IsInitialized) {
_oldCanFocus = CanFocus;
_oldTabIndex = _tabIndex;
-
-
- // TODO: Figure out why ScrollView and other tests fail if this call is put here
- // instead of the constructor.
- //InitializeFrames ();
-
}
//throw new InvalidOperationException ("The view is already initialized.");
diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs
index aa79c479c..4edf04a72 100644
--- a/Terminal.Gui/View/ViewDrawing.cs
+++ b/Terminal.Gui/View/ViewDrawing.cs
@@ -58,7 +58,7 @@ public partial class View {
/// Gets or sets whether this View will use it's SuperView's for
/// rendering any border lines. If the rendering of any borders drawn
/// by this Frame will be done by it's parent's SuperView. If (the default)
- /// this View's method will be called to render the borders.
+ /// this View's method will be called to render the borders.
///
public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
@@ -87,7 +87,14 @@ public partial class View {
/// or if is .
/// If it's overridden can return other values.
///
- public virtual Attribute GetFocusColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+ public virtual Attribute GetFocusColor ()
+ {
+ var cs = ColorScheme;
+ if (ColorScheme == null) {
+ cs = new ColorScheme ();
+ }
+ return Enabled ? cs.Focus : cs.Disabled;
+ }
///
/// Determines the current based on the value.
@@ -97,7 +104,14 @@ public partial class View {
/// or if is .
/// If it's overridden can return other values.
///
- public virtual Attribute GetHotNormalColor () => Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled;
+ public virtual Attribute GetHotNormalColor ()
+ {
+ var cs = ColorScheme;
+ if (ColorScheme == null) {
+ cs = new ColorScheme ();
+ }
+ return Enabled ? cs.HotNormal : cs.Disabled;
+ }
///
/// Displays the specified character in the specified column and row of the View.
@@ -122,7 +136,7 @@ public partial class View {
///
protected void ClearNeedsDisplay ()
{
- _needsDisplayRect = Rect.Empty;
+ _needsDisplayRect = Rect.Empty;
SubViewNeedsDisplay = false;
}
@@ -159,7 +173,7 @@ public partial class View {
} else {
var x = Math.Min (_needsDisplayRect.X, region.X);
var y = Math.Min (_needsDisplayRect.Y, region.Y);
- var w = Math.Max (_needsDisplayRect.Width, region.Width);
+ var w = Math.Max (_needsDisplayRect.Width, region.Width);
var h = Math.Max (_needsDisplayRect.Height, region.Height);
_needsDisplayRect = new Rect (x, y, w, h);
}
@@ -260,6 +274,9 @@ public partial class View {
///
public Rect ClipToBounds ()
{
+ if (Driver == null) {
+ return Rect.Empty;
+ }
var previous = Driver.Clip;
Driver.Clip = Rect.Intersect (previous, BoundsToScreen (Bounds));
return previous;
@@ -334,7 +351,7 @@ public partial class View {
/// method will cause the be prepared to be rendered.
///
///
- public virtual bool OnDrawFrames ()
+ public virtual bool OnDrawAdornments ()
{
if (!IsInitialized) {
return false;
@@ -372,13 +389,13 @@ public partial class View {
if (!CanBeVisible (this)) {
return;
}
- OnDrawFrames ();
+ OnDrawAdornments ();
var prevClip = ClipToBounds ();
if (ColorScheme != null) {
//Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
- Driver.SetAttribute (GetNormalColor ());
+ Driver?.SetAttribute (GetNormalColor ());
}
// Invoke DrawContentEvent
@@ -389,7 +406,9 @@ public partial class View {
OnDrawContent (Bounds);
}
- Driver.Clip = prevClip;
+ if (Driver != null) {
+ Driver.Clip = prevClip;
+ }
OnRenderLineCanvas ();
// Invoke DrawContentCompleteEvent
@@ -474,7 +493,7 @@ public partial class View {
{
if (NeedsDisplay) {
if (SuperView != null) {
- Clear (BoundsToScreen (Bounds));
+ Clear (BoundsToScreen (contentArea));
}
if (!string.IsNullOrEmpty (TextFormatter.Text)) {
@@ -483,7 +502,7 @@ public partial class View {
}
}
// This should NOT clear
- TextFormatter?.Draw (BoundsToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
+ TextFormatter?.Draw (BoundsToScreen (contentArea), HasFocus ? GetFocusColor () : GetNormalColor (),
HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
Rect.Empty, false);
SetSubViewNeedsDisplay ();
diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs
index 2bed5f1dd..7b602acb3 100644
--- a/Terminal.Gui/View/ViewMouse.cs
+++ b/Terminal.Gui/View/ViewMouse.cs
@@ -1,114 +1,120 @@
using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-namespace Terminal.Gui {
- public partial class View {
- ///
- /// Event fired when the view receives the mouse event for the first time.
- ///
- public event EventHandler MouseEnter;
+namespace Terminal.Gui;
- ///
- /// Event fired when the view receives a mouse event for the last time.
- ///
- public event EventHandler MouseLeave;
+public partial class View {
- ///
- /// Event fired when a mouse event is generated.
- ///
- public event EventHandler MouseClick;
+ ///
+ /// Gets or sets a value indicating whether this wants mouse position reports.
+ ///
+ /// if want mouse position reports; otherwise, .
+ public virtual bool WantMousePositionReports { get; set; }
- ///
- public override bool OnMouseEnter (MouseEvent mouseEvent)
- {
- if (!Enabled) {
- return true;
- }
+ ///
+ /// Gets or sets a value indicating whether this want continuous button pressed event.
+ ///
+ public virtual bool WantContinuousButtonPressed { get; set; }
- if (!CanBeVisible (this)) {
- return false;
- }
+ ///
+ /// Event fired when the view receives the mouse event for the first time.
+ ///
+ public event EventHandler MouseEnter;
- var args = new MouseEventEventArgs (mouseEvent);
- MouseEnter?.Invoke (this, args);
+ ///
+ /// Event fired when the view receives a mouse event for the last time.
+ ///
+ public event EventHandler MouseLeave;
- return args.Handled || base.OnMouseEnter (mouseEvent);
+ ///
+ /// Event fired when a mouse event is generated.
+ ///
+ public event EventHandler MouseClick;
+
+ ///
+ public override bool OnMouseEnter (MouseEvent mouseEvent)
+ {
+ if (!Enabled) {
+ return true;
}
- ///
- public override bool OnMouseLeave (MouseEvent mouseEvent)
- {
- if (!Enabled) {
- return true;
- }
-
- if (!CanBeVisible (this)) {
- return false;
- }
-
- var args = new MouseEventEventArgs (mouseEvent);
- MouseLeave?.Invoke (this, args);
-
- return args.Handled || base.OnMouseLeave (mouseEvent);
- }
-
- ///
- /// Method invoked when a mouse event is generated
- ///
- ///
- /// , if the event was handled, otherwise.
- public virtual bool OnMouseEvent (MouseEvent mouseEvent)
- {
- if (!Enabled) {
- return true;
- }
-
- if (!CanBeVisible (this)) {
- return false;
- }
-
- var args = new MouseEventEventArgs (mouseEvent);
- if (OnMouseClick (args))
- return true;
- if (MouseEvent (mouseEvent))
- return true;
-
- if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
- if (CanFocus && !HasFocus && SuperView != null) {
- SuperView.SetFocus (this);
- SetNeedsDisplay ();
- }
-
- return true;
- }
+ if (!CanBeVisible (this)) {
return false;
}
- ///
- /// Invokes the MouseClick event.
- ///
- protected bool OnMouseClick (MouseEventEventArgs args)
- {
- if (!Enabled) {
- return true;
- }
+ var args = new MouseEventEventArgs (mouseEvent);
+ MouseEnter?.Invoke (this, args);
- MouseClick?.Invoke (this, args);
- return args.Handled;
+ return args.Handled || base.OnMouseEnter (mouseEvent);
+ }
+
+ ///
+ public override bool OnMouseLeave (MouseEvent mouseEvent)
+ {
+ if (!Enabled) {
+ return true;
}
- ///
- /// Gets or sets a value indicating whether this wants mouse position reports.
- ///
- /// if want mouse position reports; otherwise, .
- public virtual bool WantMousePositionReports { get; set; }
+ if (!CanBeVisible (this)) {
+ return false;
+ }
- ///
- /// Gets or sets a value indicating whether this want continuous button pressed event.
- ///
- public virtual bool WantContinuousButtonPressed { get; set; }
+ var args = new MouseEventEventArgs (mouseEvent);
+ MouseLeave?.Invoke (this, args);
+
+ return args.Handled || base.OnMouseLeave (mouseEvent);
}
-}
+
+ ///
+ /// Method invoked when a mouse event is generated
+ ///
+ ///
+ /// , if the event was handled, otherwise.
+ public virtual bool OnMouseEvent (MouseEvent mouseEvent)
+ {
+ if (!Enabled) {
+ return true;
+ }
+
+ if (!CanBeVisible (this)) {
+ return false;
+ }
+
+ var args = new MouseEventEventArgs (mouseEvent);
+ if (MouseEvent (mouseEvent)) {
+ return true;
+ }
+
+ if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
+ if (CanFocus && !HasFocus && SuperView != null) {
+ SuperView.SetFocus (this);
+ SetNeedsDisplay ();
+ }
+
+ return OnMouseClick (args);
+ }
+ if (mouseEvent.Flags == MouseFlags.Button2Clicked) {
+ return OnMouseClick (args);
+ }
+ if (mouseEvent.Flags == MouseFlags.Button3Clicked) {
+ return OnMouseClick (args);
+ }
+ if (mouseEvent.Flags == MouseFlags.Button4Clicked) {
+ return OnMouseClick (args);
+ }
+
+ return false;
+ }
+
+ ///
+ /// Invokes the MouseClick event.
+ ///
+ protected bool OnMouseClick (MouseEventEventArgs args)
+ {
+ if (!Enabled) {
+ return true;
+ }
+
+ MouseClick?.Invoke (this, args);
+ return args.Handled;
+ }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs
index 391aa7da9..4cbc95c10 100644
--- a/Terminal.Gui/View/ViewSubViews.cs
+++ b/Terminal.Gui/View/ViewSubViews.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-namespace Terminal.Gui;
+namespace Terminal.Gui;
public partial class View {
static readonly IList _empty = new List (0).AsReadOnly ();
@@ -71,16 +71,16 @@ public partial class View {
_addingView = true;
if (SuperView?.CanFocus == false) {
SuperView._addingView = true;
- SuperView.CanFocus = true;
+ SuperView.CanFocus = true;
SuperView._addingView = false;
}
- CanFocus = true;
+ CanFocus = true;
view._tabIndex = _tabIndexes.IndexOf (view);
- _addingView = false;
+ _addingView = false;
}
if (view.Enabled && !Enabled) {
view._oldEnabled = true;
- view.Enabled = false;
+ view.Enabled = false;
}
OnAdded (new SuperViewChangedEventArgs (this, view));
@@ -88,7 +88,6 @@ public partial class View {
view.BeginInit ();
view.EndInit ();
}
-
CheckDimAuto ();
SetNeedsLayout ();
SetNeedsDisplay ();
@@ -158,7 +157,7 @@ public partial class View {
_subviews.Remove (view);
_tabIndexes.Remove (view);
view._superView = null;
- view._tabIndex = -1;
+ view._tabIndex = -1;
SetNeedsLayout ();
SetNeedsDisplay ();
@@ -379,14 +378,14 @@ public partial class View {
if (!value) {
view._oldCanFocus = view.CanFocus;
view._oldTabIndex = view._tabIndex;
- view.CanFocus = false;
- view._tabIndex = -1;
+ view.CanFocus = false;
+ view._tabIndex = -1;
} else {
if (_addingView) {
view._addingView = true;
}
- view.CanFocus = view._oldCanFocus;
- view._tabIndex = view._oldTabIndex;
+ view.CanFocus = view._oldCanFocus;
+ view._tabIndex = view._oldTabIndex;
view._addingView = false;
}
}
diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs
index d8114bdc9..6f412bf27 100644
--- a/Terminal.Gui/View/ViewText.cs
+++ b/Terminal.Gui/View/ViewText.cs
@@ -193,7 +193,7 @@ public partial class View {
if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) {
return false;
}
-
+
switch (TextFormatter.IsVerticalDirection (TextDirection)) {
case true:
var colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1);
@@ -307,7 +307,7 @@ public partial class View {
x = Bounds.X;
y = Bounds.Y;
}
- var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
+ var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal);
int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical);
return new Size (newWidth, newHeight);
diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs
index e215cb4bb..c22ec013d 100644
--- a/Terminal.Gui/Views/Button.cs
+++ b/Terminal.Gui/Views/Button.cs
@@ -1,4 +1,4 @@
-//
+//
// Button.cs: Button control
//
// Authors:
@@ -10,65 +10,64 @@ using System.Text;
namespace Terminal.Gui;
///
-/// Button is a that provides an item that invokes raises the event.
+/// Button is a that provides an item that invokes raises the event.
///
///
-///
-/// Provides a button showing text that raises the event when clicked on with a mouse
-/// or when the user presses SPACE, ENTER, or the . The hot key is the first letter or digit following the first underscore ('_')
-/// in the button text.
-///
-///
-/// Use to change the hot key specifier from the default of ('_').
-///
-///
-/// If no hot key specifier is found, the first uppercase letter encountered will be used as the hot key.
-///
-///
-/// When the button is configured as the default () and the user presses
-/// the ENTER key, if no other processes the key, the 's
-/// event will will be fired.
-///
+///
+/// Provides a button showing text that raises the event when clicked on with a mouse
+/// or when the user presses SPACE, ENTER, or the . The hot key is the first letter or
+/// digit following the first underscore ('_')
+/// in the button text.
+///
+///
+/// Use to change the hot key specifier from the default of ('_').
+///
+///
+/// If no hot key specifier is found, the first uppercase letter encountered will be used as the hot key.
+///
+///
+/// When the button is configured as the default () and the user presses
+/// the ENTER key, if no other processes the key, the 's
+/// event will will be fired.
+///
///
public class Button : View {
bool _isDefault;
Rune _leftBracket;
- Rune _rightBracket;
Rune _leftDefault;
+ Rune _rightBracket;
Rune _rightDefault;
///
- /// Initializes a new instance of using layout.
+ /// Initializes a new instance of using layout.
///
///
- /// The width of the is computed based on the
- /// text length. The height will always be 1.
+ /// The width of the is computed based on the
+ /// text length. The height will always be 1.
///
- public Button () : this (text: string.Empty, is_default: false) { }
+ public Button () : this (string.Empty, false) { }
///
- /// Initializes a new instance of using layout.
+ /// Initializes a new instance of using layout.
///
///
- /// The width of the is computed based on the
- /// text length. The height will always be 1.
+ /// The width of the is computed based on the
+ /// text length. The height will always be 1.
///
/// The button's text
///
- /// If true, a special decoration is used, and the user pressing the enter key
- /// in a will implicitly activate this button.
+ /// If true, a special decoration is used, and the user pressing the enter key
+ /// in a will implicitly activate this button.
///
- public Button (string text, bool is_default = false) : base (text)
- {
- SetInitialProperties (text, is_default);
- }
+ public Button (string text, bool is_default = false) : base (text) => SetInitialProperties (text, is_default);
///
- /// Initializes a new instance of using layout, based on the given text
+ /// Initializes a new instance of using layout, based on the given
+ /// text
///
///
- /// The width of the is computed based on the
- /// text length. The height will always be 1.
+ /// The width of the is computed based on the
+ /// text length. The height will always be 1.
///
/// X position where the button will be shown.
/// Y position where the button will be shown.
@@ -76,53 +75,22 @@ public class Button : View {
public Button (int x, int y, string text) : this (x, y, text, false) { }
///
- /// Initializes a new instance of using layout, based on the given text.
+ /// Initializes a new instance of using layout, based on the given
+ /// text.
///
///
- /// The width of the is computed based on the
- /// text length. The height will always be 1.
+ /// The width of the is computed based on the
+ /// text length. The height will always be 1.
///
/// X position where the button will be shown.
/// Y position where the button will be shown.
/// The button's text
///
- /// If true, a special decoration is used, and the user pressing the enter key
- /// in a will implicitly activate this button.
+ /// If true, a special decoration is used, and the user pressing the enter key
+ /// in a will implicitly activate this button.
///
public Button (int x, int y, string text, bool is_default)
- : base (new Rect (x, y, text.GetRuneCount () + 4 + (is_default ? 2 : 0), 1), text)
- {
- SetInitialProperties (text, is_default);
- }
-
- // TODO: v2 - Remove constructors with parameters
- ///
- /// Private helper to set the initial properties of the View that were provided via constructors.
- ///
- ///
- ///
- void SetInitialProperties (string text, bool is_default)
- {
- TextAlignment = TextAlignment.Centered;
- VerticalTextAlignment = VerticalTextAlignment.Middle;
-
- HotKeySpecifier = new Rune ('_');
-
- _leftBracket = CM.Glyphs.LeftBracket;
- _rightBracket = CM.Glyphs.RightBracket;
- _leftDefault = CM.Glyphs.LeftDefaultIndicator;
- _rightDefault = CM.Glyphs.RightDefaultIndicator;
-
- CanFocus = true;
- AutoSize = true;
- _isDefault = is_default;
- Text = text ?? string.Empty;
-
- // Override default behavior of View
- // Command.Default sets focus
- AddCommand (Command.Accept, () => { OnClicked (); return true; });
- KeyBindings.Add (Key.Space, Command.Default, Command.Accept);
- }
+ : base (new Rect (x, y, text.GetRuneCount () + 4 + (is_default ? 2 : 0), 1), text) => SetInitialProperties (text, is_default);
///
/// Gets or sets whether the is the default action to activate in a dialog.
@@ -147,15 +115,47 @@ public class Button : View {
///
public bool NoPadding { get; set; }
+ // TODO: v2 - Remove constructors with parameters
+ ///
+ /// Private helper to set the initial properties of the View that were provided via constructors.
+ ///
+ ///
+ ///
+ void SetInitialProperties (string text, bool is_default)
+ {
+ TextAlignment = TextAlignment.Centered;
+ VerticalTextAlignment = VerticalTextAlignment.Middle;
+
+ HotKeySpecifier = new Rune ('_');
+
+ _leftBracket = Glyphs.LeftBracket;
+ _rightBracket = Glyphs.RightBracket;
+ _leftDefault = Glyphs.LeftDefaultIndicator;
+ _rightDefault = Glyphs.RightDefaultIndicator;
+
+ CanFocus = true;
+ AutoSize = true;
+ _isDefault = is_default;
+ Text = text ?? string.Empty;
+
+ // Override default behavior of View
+ // Command.Default sets focus
+ AddCommand (Command.Accept, () => {
+ OnClicked ();
+ return true;
+ });
+ KeyBindings.Add (Key.Space, Command.Default, Command.Accept);
+ KeyBindings.Add (Key.Enter, Command.Default, Command.Accept);
+ }
+
///
protected override void UpdateTextFormatterText ()
{
if (NoDecorations) {
TextFormatter.Text = Text;
- } else
- if (IsDefault)
+ } else if (IsDefault) {
TextFormatter.Text = $"{_leftBracket}{_leftDefault} {Text} {_rightDefault}{_rightBracket}";
- else {
+ } else {
if (NoPadding) {
TextFormatter.Text = $"{_leftBracket}{Text}{_rightBracket}";
} else {
@@ -164,31 +164,20 @@ public class Button : View {
}
}
- bool AcceptKey ()
- {
- //if (!HasFocus) {
- // SetFocus ();
- //}
- OnClicked ();
- return true;
- }
///
/// Virtual method to invoke the event.
///
- public virtual void OnClicked ()
- {
- Clicked?.Invoke (this, EventArgs.Empty);
- }
+ public virtual void OnClicked () => Clicked?.Invoke (this, EventArgs.Empty);
///
- /// The event fired when the user clicks the primary mouse button within the Bounds of this
- /// or if the user presses the action key while this view is focused. (TODO: IsDefault)
+ /// The event fired when the user clicks the primary mouse button within the Bounds of this
+ /// or if the user presses the action key while this view is focused. (TODO: IsDefault)
///
///
- /// Client code can hook up to this event, it is
- /// raised when the button is activated either with
- /// the mouse or the keyboard.
+ /// Client code can hook up to this event, it is
+ /// raised when the button is activated either with
+ /// the mouse or the keyboard.
///
public event EventHandler Clicked;
@@ -214,7 +203,7 @@ public class Button : View {
public override void PositionCursor ()
{
if (HotKey.IsValid && Text != "") {
- for (int i = 0; i < TextFormatter.Text.GetRuneCount (); i++) {
+ for (var i = 0; i < TextFormatter.Text.GetRuneCount (); i++) {
if (TextFormatter.Text [i] == Text [0]) {
Move (i, 0);
return;
@@ -231,4 +220,4 @@ public class Button : View {
return base.OnEnter (view);
}
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs
index f33ee067b..b4a5dc923 100644
--- a/Terminal.Gui/Views/ColorPicker.cs
+++ b/Terminal.Gui/Views/ColorPicker.cs
@@ -1,7 +1,7 @@
using System;
using System.Text;
-namespace Terminal.Gui;
+namespace Terminal.Gui;
///
/// Event arguments for the events.
@@ -94,9 +94,9 @@ public class ColorPicker : View {
var prev = (ColorName)_selectColorIndex;
_selectColorIndex = (int)value;
ColorChanged?.Invoke (this, new ColorEventArgs {
- PreviousColor = new Color (prev),
- Color = new Color (value)
- });
+ PreviousColor = new Color (prev),
+ Color = new Color (value)
+ });
SetNeedsDisplay ();
}
}
@@ -112,8 +112,8 @@ public class ColorPicker : View {
AddCommands ();
AddKeyBindings ();
LayoutStarted += (o, a) => {
- var thickness = GetFramesThickness ();
- Width = _cols * BoxWidth + thickness.Vertical;
+ var thickness = GetAdornmentsThickness ();
+ Width = _cols * BoxWidth + thickness.Vertical;
Height = _rows * BoxHeight + thickness.Horizontal;
};
}
@@ -123,9 +123,9 @@ public class ColorPicker : View {
///
void AddCommands ()
{
- AddCommand (Command.Left, () => MoveLeft ());
- AddCommand (Command.Right, () => MoveRight ());
- AddCommand (Command.LineUp, () => MoveUp ());
+ AddCommand (Command.Left, () => MoveLeft ());
+ AddCommand (Command.Right, () => MoveRight ());
+ AddCommand (Command.LineUp, () => MoveUp ());
AddCommand (Command.LineDown, () => MoveDown ());
}
@@ -134,10 +134,10 @@ public class ColorPicker : View {
///
void AddKeyBindings ()
{
- KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+ KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
KeyBindings.Add (KeyCode.CursorRight, Command.Right);
- KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
- KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+ KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+ KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
}
///
@@ -190,10 +190,10 @@ public class ColorPicker : View {
} else if (rect.Height == 1) {
lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
} else {
- lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+ lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
lc.AddLine (new Point (rect.Location.X, rect.Location.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, LineStyle.Dotted);
- lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+ lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
lc.AddLine (new Point (rect.Location.X + rect.Width - 1, rect.Location.Y), rect.Height, Orientation.Vertical, LineStyle.Dotted);
}
foreach (var p in lc.GetMap ()) {
diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs
index af7ef4c78..60e2f8b1a 100644
--- a/Terminal.Gui/Views/ComboBox.cs
+++ b/Terminal.Gui/Views/ComboBox.cs
@@ -255,7 +255,7 @@ public class ComboBox : View {
public ComboBox (Rect rect, IList source) : base (rect)
{
_search = new TextField ("") { Width = rect.Width };
- _listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { ColorScheme = Colors.Base };
+ _listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { ColorScheme = Colors.ColorSchemes ["Base"] };
SetInitialProperties ();
SetSource (source);
@@ -268,7 +268,7 @@ public class ComboBox : View {
public ComboBox (IList source) : this (string.Empty)
{
_search = new TextField ("");
- _listview = new ComboListView (this, source, HideDropdownListOnClick) { ColorScheme = Colors.Base };
+ _listview = new ComboListView (this, source, HideDropdownListOnClick) { ColorScheme = Colors.ColorSchemes ["Base"] };
SetInitialProperties ();
SetSource (source);
@@ -396,7 +396,9 @@ public class ComboBox : View {
_search.ReadOnly = value;
if (_search.ReadOnly) {
if (_search.ColorScheme != null) {
- _search.ColorScheme.Normal = _search.ColorScheme.Focus;
+ _search.ColorScheme = new ColorScheme (_search.ColorScheme) {
+ Normal = _search.ColorScheme.Focus
+ };
}
}
}
diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs
index 5fc87abf3..389911769 100644
--- a/Terminal.Gui/Views/DateField.cs
+++ b/Terminal.Gui/Views/DateField.cs
@@ -20,16 +20,9 @@ namespace Terminal.Gui;
///
public class DateField : TextField {
DateTime _date;
- bool _isShort;
- int _longFieldLen = 10;
- int _shortFieldLen = 8;
+ int _fieldLen = 10;
string _sepChar;
- string _longFormat;
- string _shortFormat;
-
- int _fieldLen => _isShort ? _shortFieldLen : _longFieldLen;
-
- string _format => _isShort ? _shortFormat : _longFormat;
+ string _format;
///
/// DateChanged event, raised when the property has changed.
@@ -42,15 +35,6 @@ public class DateField : TextField {
///
public event EventHandler> DateChanged;
- ///
- /// Initializes a new instance of using layout.
- ///
- /// The x coordinate.
- /// The y coordinate.
- /// Initial date contents.
- /// If true, shows only two digits for the year.
- public DateField (int x, int y, DateTime date, bool isShort = false) : base (x, y, isShort ? 10 : 12, "") => SetInitialProperties (date, isShort);
-
///
/// Initializes a new instance of using layout.
///
@@ -66,16 +50,14 @@ public class DateField : TextField {
SetInitialProperties (date);
}
- void SetInitialProperties (DateTime date, bool isShort = false)
+ void SetInitialProperties (DateTime date)
{
var cultureInfo = CultureInfo.CurrentCulture;
_sepChar = cultureInfo.DateTimeFormat.DateSeparator;
- _longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
- _shortFormat = GetShortFormat (_longFormat);
- this._isShort = isShort;
+ _format = $" {cultureInfo.DateTimeFormat.ShortDatePattern}";
Date = date;
CursorPosition = 1;
- TextChanged += DateField_Changed;
+ TextChanging += DateField_Changing;
// Things this view knows how to do
AddCommand (Command.DeleteCharRight, () => {
@@ -109,7 +91,6 @@ public class DateField : TextField {
KeyBindings.Add (Key.CursorRight, Command.Right);
KeyBindings.Add (Key.F.WithCtrl, Command.Right);
-
}
///
@@ -127,45 +108,33 @@ public class DateField : TextField {
return false;
}
- void DateField_Changed (object sender, TextChangedEventArgs e)
+ void DateField_Changing (object sender, TextChangingEventArgs e)
{
try {
- var date = GetInvarianteDate (Text, _isShort);
- if ($" {date}" != Text) {
- Text = $" {date}";
+ var cultureInfo = CultureInfo.CurrentCulture;
+ DateTimeFormatInfo ccFmt = cultureInfo.DateTimeFormat;
+ int spaces = 0;
+ for (int i = 0; i < e.NewText.Length; i++) {
+ if (e.NewText [i] == ' ') {
+ spaces++;
+ } else {
+ break;
+ }
}
- if (_isShort) {
- date = GetInvarianteDate (Text, false);
- }
- if (!DateTime.TryParseExact (date, GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out var result)) {
- Text = e.OldValue;
+ spaces += _fieldLen;
+ string trimedText = e.NewText [..spaces];
+ spaces -= _fieldLen;
+ trimedText = trimedText.Replace (new string (' ', spaces), " ");
+ var date = Convert.ToDateTime (trimedText, ccFmt).ToString (ccFmt.ShortDatePattern);
+ if ($" {date}" != e.NewText) {
+ e.NewText = $" {date}";
}
+ AdjCursorPosition (CursorPosition, true);
} catch (Exception) {
- Text = e.OldValue;
+ e.Cancel = true;
}
}
- string GetInvarianteFormat () => $"MM{_sepChar}dd{_sepChar}yyyy";
-
- string GetLongFormat (string lf)
- {
- string [] frm = lf.Split (_sepChar);
- for (int i = 0; i < frm.Length; i++) {
- if (frm [i].Contains ("M") && frm [i].GetRuneCount () < 2) {
- lf = lf.Replace ("M", "MM");
- }
- if (frm [i].Contains ("d") && frm [i].GetRuneCount () < 2) {
- lf = lf.Replace ("d", "dd");
- }
- if (frm [i].Contains ("y") && frm [i].GetRuneCount () < 4) {
- lf = lf.Replace ("yy", "yyyy");
- }
- }
- return $" {lf}";
- }
-
- string GetShortFormat (string lf) => lf.Replace ("yyyy", "yy");
-
///
/// Gets or sets the date of the .
///
@@ -188,28 +157,6 @@ public class DateField : TextField {
}
}
- ///
- /// Get or set the date format for the widget.
- ///
- public bool IsShortFormat {
- get => _isShort;
- set {
- _isShort = value;
- if (_isShort) {
- Width = 10;
- } else {
- Width = 12;
- }
- bool ro = ReadOnly;
- if (ro) {
- ReadOnly = false;
- }
- SetText (Text);
- ReadOnly = ro;
- SetNeedsDisplay ();
- }
- }
-
///
public override int CursorPosition {
get => base.CursorPosition;
@@ -230,7 +177,7 @@ public class DateField : TextField {
var newText = text.GetRange (0, CursorPosition);
newText.Add (key);
if (CursorPosition < _fieldLen) {
- newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
+ newText = [.. newText, .. text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))];
}
return SetText (StringExtensions.ToString (newText));
}
@@ -310,18 +257,12 @@ public class DateField : TextField {
{
string date = " ";
for (int i = 0; i < fm.Length; i++) {
- if (fm [i].Contains ("M")) {
+ if (fm [i].Contains ('M')) {
date += $"{month,2:00}";
- } else if (fm [i].Contains ("d")) {
+ } else if (fm [i].Contains ('d')) {
date += $"{day,2:00}";
} else {
- if (_isShort && year.ToString ().Length == 4) {
- date += $"{year.ToString ().Substring (2, 2)}";
- } else if (_isShort) {
- date += $"{year,2:00}";
- } else {
- date += $"{year,4:0000}";
- }
+ date += $"{year,4:0000}";
}
if (i < 2) {
date += $"{_sepChar}";
@@ -330,40 +271,7 @@ public class DateField : TextField {
return date;
}
- string GetInvarianteDate (string text, bool isShort)
- {
- string [] vals = text.Split (_sepChar);
- string [] frm = (isShort ? $"MM{_sepChar}dd{_sepChar}yy" : GetInvarianteFormat ()).Split (_sepChar);
- string [] date = { null, null, null };
-
- for (int i = 0; i < frm.Length; i++) {
- if (frm [i].Contains ("M")) {
- date [0] = vals [i].Trim ();
- } else if (frm [i].Contains ("d")) {
- date [1] = vals [i].Trim ();
- } else {
- string yearString;
- if (isShort && vals [i].Length > 2) {
- yearString = vals [i].Substring (0, 2);
- } else if (!isShort && vals [i].Length > 4) {
- yearString = vals [i].Substring (0, 4);
- } else {
- yearString = vals [i].Trim ();
- }
- var year = int.Parse (yearString);
- if (isShort && year.ToString ().Length == 4) {
- date [2] = year.ToString ().Substring (2, 2);
- } else if (isShort) {
- date [2] = year.ToString ();
- } else {
- date [2] = $"{year,4:0000}";
- }
- }
- }
- return $"{date [0]}{_sepChar}{date [1]}{_sepChar}{date [2]}";
- }
-
- int GetFormatIndex (string [] fm, string t)
+ static int GetFormatIndex (string [] fm, string t)
{
int idx = -1;
for (int i = 0; i < fm.Length; i++) {
@@ -381,9 +289,8 @@ public class DateField : TextField {
CursorPosition = _fieldLen;
return;
}
- if (Text [++CursorPosition] == _sepChar.ToCharArray () [0]) {
- CursorPosition++;
- }
+ CursorPosition++;
+ AdjCursorPosition (CursorPosition);
}
void DecCursorPosition ()
@@ -392,15 +299,29 @@ public class DateField : TextField {
CursorPosition = 1;
return;
}
- if (Text [--CursorPosition] == _sepChar.ToCharArray () [0]) {
- CursorPosition--;
- }
+ CursorPosition--;
+ AdjCursorPosition (CursorPosition, false);
}
- void AdjCursorPosition ()
+ void AdjCursorPosition (int point, bool increment = true)
{
- if (Text [CursorPosition] == _sepChar.ToCharArray () [0]) {
- CursorPosition++;
+ var newPoint = point;
+ if (point > _fieldLen) {
+ newPoint = _fieldLen;
+ }
+ if (point < 1) {
+ newPoint = 1;
+ }
+ if (newPoint != point) {
+ CursorPosition = newPoint;
+ }
+
+ while (Text [CursorPosition] == _sepChar [0]) {
+ if (increment) {
+ CursorPosition++;
+ } else {
+ CursorPosition--;
+ }
}
}
@@ -461,23 +382,13 @@ public class DateField : TextField {
///
public override bool MouseEvent (MouseEvent ev)
{
- if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked)) {
- return false;
- }
- if (!HasFocus) {
- SetFocus ();
- }
+ var result = base.MouseEvent (ev);
- int point = ev.X;
- if (point > _fieldLen) {
- point = _fieldLen;
+ if (result && SelectedLength == 0 && ev.Flags.HasFlag (MouseFlags.Button1Pressed)) {
+ int point = ev.X;
+ AdjCursorPosition (point, true);
}
- if (point < 1) {
- point = 1;
- }
- CursorPosition = point;
- AdjCursorPosition ();
- return true;
+ return result;
}
///
diff --git a/Terminal.Gui/Views/DatePicker.cs b/Terminal.Gui/Views/DatePicker.cs
new file mode 100644
index 000000000..07e6b9e99
--- /dev/null
+++ b/Terminal.Gui/Views/DatePicker.cs
@@ -0,0 +1,240 @@
+//
+// DatePicker.cs: DatePicker control
+//
+// Author: Maciej Winnik
+//
+using System;
+using System.Data;
+using System.Globalization;
+using System.Linq;
+
+namespace Terminal.Gui;
+
+///
+/// The Date Picker.
+///
+public class DatePicker : View {
+
+ private DateField _dateField;
+ private Label _dateLabel;
+ private TableView _calendar;
+ private DataTable _table;
+ private Button _nextMonthButton;
+ private Button _previousMonthButton;
+
+ private DateTime _date = DateTime.Now;
+
+ ///
+ /// Format of date. The default is MM/dd/yyyy.
+ ///
+ public string Format { get; set; } = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
+
+ ///
+ /// Get or set the date.
+ ///
+ public DateTime Date {
+ get => _date;
+ set {
+ _date = value;
+ Text = _date.ToString (Format);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ public DatePicker () => SetInitialProperties (_date);
+
+ ///
+ /// Initializes a new instance of with the specified date.
+ ///
+ public DatePicker (DateTime date)
+ {
+ SetInitialProperties (date);
+ }
+
+ ///
+ /// Initializes a new instance of with the specified date and format.
+ ///
+ public DatePicker (DateTime date, string format)
+ {
+ Format = format;
+ SetInitialProperties (date);
+ }
+
+ private void SetInitialProperties (DateTime date)
+ {
+ Title = "Date Picker";
+ BorderStyle = LineStyle.Single;
+ Date = date;
+ _dateLabel = new Label ("Date: ") {
+ X = 0,
+ Y = 0,
+ Height = 1,
+ };
+
+ _dateField = new DateField (DateTime.Now) {
+ X = Pos.Right (_dateLabel),
+ Y = 0,
+ Width = Dim.Fill (1),
+ Height = 1
+ };
+
+ _calendar = new TableView () {
+ X = 0,
+ Y = Pos.Bottom (_dateLabel),
+ Height = 11,
+ Style = new TableStyle {
+ ShowHeaders = true,
+ ShowHorizontalBottomline = true,
+ ShowVerticalCellLines = true,
+ ExpandLastColumn = true,
+ }
+ };
+
+ _previousMonthButton = new Button (GetBackButtonText ()) {
+ X = Pos.Center () - 4,
+ Y = Pos.Bottom (_calendar) - 1,
+ Height = 1,
+ Width = CalculateCalendarWidth () / 2
+ };
+
+ _previousMonthButton.Clicked += (sender, e) => {
+ Date = _date.AddMonths (-1);
+ CreateCalendar ();
+ _dateField.Date = Date;
+ };
+
+ _nextMonthButton = new Button (GetForwardButtonText ()) {
+ X = Pos.Right (_previousMonthButton) + 2,
+ Y = Pos.Bottom (_calendar) - 1,
+ Height = 1,
+ Width = CalculateCalendarWidth () / 2
+ };
+
+ _nextMonthButton.Clicked += (sender, e) => {
+ Date = _date.AddMonths (1);
+ CreateCalendar ();
+ _dateField.Date = Date;
+ };
+
+ CreateCalendar ();
+ SelectDayOnCalendar (_date.Day);
+
+ _calendar.CellActivated += (sender, e) => {
+ var dayValue = _table.Rows [e.Row] [e.Col];
+ if (dayValue is null) {
+ return;
+ }
+ bool isDay = int.TryParse (dayValue.ToString (), out int day);
+ if (!isDay) {
+ return;
+ }
+ ChangeDayDate (day);
+ SelectDayOnCalendar (day);
+ Text = _date.ToString (Format);
+
+ };
+
+ Width = CalculateCalendarWidth () + 2;
+ Height = _calendar.Height + 3;
+
+ _dateField.DateChanged += DateField_DateChanged;
+
+ Add (_dateLabel, _dateField, _calendar, _previousMonthButton, _nextMonthButton);
+ }
+
+ private void DateField_DateChanged (object sender, DateTimeEventArgs e)
+ {
+ if (e.NewValue.Date.Day != _date.Day) {
+ SelectDayOnCalendar (e.NewValue.Day);
+ }
+ Date = e.NewValue;
+ CreateCalendar ();
+ SelectDayOnCalendar (_date.Day);
+ }
+
+ private void CreateCalendar ()
+ {
+ _calendar.Table = new DataTableSource (_table = CreateDataTable (_date.Month, _date.Year));
+ }
+
+ private void ChangeDayDate (int day)
+ {
+ _date = new DateTime (_date.Year, _date.Month, day);
+ _dateField.Date = _date;
+ CreateCalendar ();
+ }
+
+ private DataTable CreateDataTable (int month, int year)
+ {
+ _table = new DataTable ();
+ GenerateCalendarLabels ();
+ int amountOfDaysInMonth = DateTime.DaysInMonth (year, month);
+ DateTime dateValue = new DateTime (year, month, 1);
+ var dayOfWeek = dateValue.DayOfWeek;
+
+ _table.Rows.Add (new object [6]);
+ for (int i = 1; i <= amountOfDaysInMonth; i++) {
+ _table.Rows [^1] [(int)dayOfWeek] = i;
+ if (dayOfWeek == DayOfWeek.Saturday && i != amountOfDaysInMonth) {
+ _table.Rows.Add (new object [7]);
+ }
+ dayOfWeek = dayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : dayOfWeek + 1;
+ }
+ int missingRows = 6 - _table.Rows.Count;
+ for (int i = 0; i < missingRows; i++) {
+ _table.Rows.Add (new object [7]);
+ }
+
+ return _table;
+ }
+
+ private void GenerateCalendarLabels ()
+ {
+ _calendar.Style.ColumnStyles.Clear ();
+ for (int i = 0; i < 7; i++) {
+ var abbreviatedDayName = CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedDayName ((DayOfWeek)i);
+ _calendar.Style.ColumnStyles.Add (i, new ColumnStyle () {
+ MaxWidth = abbreviatedDayName.Length,
+ MinWidth = abbreviatedDayName.Length,
+ MinAcceptableWidth = abbreviatedDayName.Length
+ });
+ _table.Columns.Add (abbreviatedDayName);
+ }
+ _calendar.Width = CalculateCalendarWidth ();
+ }
+
+ private int CalculateCalendarWidth ()
+ {
+ return _calendar.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 7;
+ }
+
+ private void SelectDayOnCalendar (int day)
+ {
+ for (int i = 0; i < _table.Rows.Count; i++) {
+ for (int j = 0; j < _table.Columns.Count; j++) {
+ if (_table.Rows [i] [j].ToString () == day.ToString ()) {
+ _calendar.SetSelection (j, i, false);
+ return;
+ }
+ }
+ }
+ }
+
+ private string GetForwardButtonText () => Glyphs.RightArrow.ToString () + Glyphs.RightArrow.ToString ();
+
+ private string GetBackButtonText () => Glyphs.LeftArrow.ToString () + Glyphs.LeftArrow.ToString ();
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ _dateLabel.Dispose ();
+ _calendar.Dispose ();
+ _dateField.Dispose ();
+ _table.Dispose ();
+ _previousMonthButton.Dispose ();
+ _nextMonthButton.Dispose ();
+ base.Dispose (disposing);
+ }
+}
diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs
index 1f64e19d0..c64331c5e 100644
--- a/Terminal.Gui/Views/Dialog.cs
+++ b/Terminal.Gui/Views/Dialog.cs
@@ -7,7 +7,7 @@ namespace Terminal.Gui;
///
/// The is a that by default is centered and contains one
-/// or more s. It defaults to the color scheme and has a 1 cell padding around the edges.
+/// or more s. It defaults to the Colors.ColorSchemes ["Dialog"] color scheme and has a 1 cell padding around the edges.
///
///
/// To run the modally, create the , and pass it to .
@@ -65,11 +65,13 @@ public class Dialog : Window {
Width = Dim.Percent (85);// Dim.Auto (min: Dim.Percent (10));
Height = Dim.Percent (85);//Dim.Auto (min: Dim.Percent (50));
- ColorScheme = Colors.Dialog;
+ ColorScheme = Colors.ColorSchemes ["Dialog"];
Modal = true;
ButtonAlignment = DefaultButtonAlignment;
+ KeyBindings.Add (Key.Esc, Command.QuitToplevel);
+
if (buttons != null) {
foreach (var b in buttons) {
AddButton (b);
@@ -104,6 +106,7 @@ public class Dialog : Window {
//button.AutoSize = false; // BUGBUG: v2 - Hack to get around autosize not accounting for Margin?
buttons.Add (button);
Add (button);
+
SetNeedsDisplay ();
if (IsInitialized) {
LayoutSubviews ();
@@ -228,16 +231,4 @@ public class Dialog : Window {
break;
}
}
-
- // BUGBUG: Why is this not handled by a key binding???
- ///
- public override bool OnProcessKeyDown (Key a)
- {
- switch (a.KeyCode) {
- case KeyCode.Esc:
- Application.RequestStop (this);
- return true;
- }
- return false;
- }
}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs
index 6ed57b858..c2b0b4533 100644
--- a/Terminal.Gui/Views/FileDialog.cs
+++ b/Terminal.Gui/Views/FileDialog.cs
@@ -8,7 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using Terminal.Gui.Resources;
-namespace Terminal.Gui;
+namespace Terminal.Gui;
///
/// Modal dialog for selecting files/directories. Has auto-complete and expandable
@@ -91,7 +91,7 @@ public class FileDialog : Dialog {
btnOk.Clicked += (s, e) => Accept (true);
btnOk.KeyDown += (s, k) => {
NavigateIf (k, KeyCode.CursorLeft, btnCancel);
- NavigateIf (k, KeyCode.CursorUp, tableView);
+ NavigateIf (k, KeyCode.CursorUp, tableView);
};
btnCancel = new Button (Strings.btnCancel) {
@@ -99,8 +99,8 @@ public class FileDialog : Dialog {
X = Pos.Right (btnOk) + 1
};
btnCancel.KeyDown += (s, k) => {
- NavigateIf (k, KeyCode.CursorLeft, btnToggleSplitterCollapse);
- NavigateIf (k, KeyCode.CursorUp, tableView);
+ NavigateIf (k, KeyCode.CursorLeft, btnToggleSplitterCollapse);
+ NavigateIf (k, KeyCode.CursorUp, tableView);
NavigateIf (k, KeyCode.CursorRight, btnOk);
};
btnCancel.Clicked += (s, e) => {
@@ -265,10 +265,10 @@ public class FileDialog : Dialog {
tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k);
tableView.SelectedCellChanged += TableView_SelectedCellChanged;
- tableView.KeyBindings.Add (KeyCode.Home, Command.TopHome);
- tableView.KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+ tableView.KeyBindings.Add (KeyCode.Home, Command.TopHome);
+ tableView.KeyBindings.Add (KeyCode.End, Command.BottomEnd);
tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend);
- tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend);
+ tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend);
treeView.KeyDown += (s, k) => {
@@ -475,7 +475,7 @@ public class FileDialog : Dialog {
string GetUpButtonText () => Style.UseUnicodeCharacters ? "◭" : "▲";
string GetToggleSplitterText (bool isExpanded) => isExpanded ?
- new string ((char)Glyphs.LeftArrow.Value, 2) :
+ new string ((char)Glyphs.LeftArrow.Value, 2) :
new string ((char)Glyphs.RightArrow.Value, 2);
void Delete ()
@@ -657,7 +657,9 @@ public class FileDialog : Dialog {
tbPath.Caption = Style.PathCaption;
tbFind.Caption = Style.SearchCaption;
- tbPath.Autocomplete.ColorScheme.Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background);
+ tbPath.Autocomplete.ColorScheme = new ColorScheme (tbPath.ColorScheme) {
+ Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background)
+ };
_treeRoots = Style.TreeRootGetter ();
Style.IconProvider.IsOpenGetter = treeView.IsExpanded;
@@ -1183,10 +1185,10 @@ public class FileDialog : Dialog {
// TODO: Add some kind of cache for this
return new ColorScheme {
- Normal = new Attribute (color, black),
+ Normal = new Attribute (color, black),
HotNormal = new Attribute (color, black),
- Focus = new Attribute (black, color),
- HotFocus = new Attribute (black, color)
+ Focus = new Attribute (black, color),
+ HotFocus = new Attribute (black, color)
};
}
@@ -1273,7 +1275,7 @@ public class FileDialog : Dialog {
// This portion is flexible based on the column clicked (e.g. alphabetical)
var ordered =
currentSortIsAsc ?
- forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)) :
+ forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)) :
forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f));
State.Children = ordered.ToArray ();
diff --git a/Terminal.Gui/Views/FileSystemColorProvider.cs b/Terminal.Gui/Views/FileSystemColorProvider.cs
index 6c78f2301..1196cf315 100644
--- a/Terminal.Gui/Views/FileSystemColorProvider.cs
+++ b/Terminal.Gui/Views/FileSystemColorProvider.cs
@@ -14,7 +14,7 @@ namespace Terminal.Gui {
///
///
///
- public Color GetColor (IFileSystemInfo file)
+ public Color? GetColor (IFileSystemInfo file)
{
if (FilenameToColor.ContainsKey (file.Name)) {
return FilenameToColor [file.Name];
@@ -443,8 +443,10 @@ namespace Terminal.Gui {
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/FrameView.cs b/Terminal.Gui/Views/FrameView.cs
index 9e9b4f33d..c5b62d74c 100644
--- a/Terminal.Gui/Views/FrameView.cs
+++ b/Terminal.Gui/Views/FrameView.cs
@@ -50,7 +50,7 @@ namespace Terminal.Gui {
{
this.Title = title;
Border.Thickness = new Thickness (1);
- Border.BorderStyle = DefaultBorderStyle;
+ Border.LineStyle = DefaultBorderStyle;
//Border.ColorScheme = ColorScheme;
Border.Data = "Border";
}
diff --git a/Terminal.Gui/Views/GraphView/Annotations.cs b/Terminal.Gui/Views/GraphView/Annotations.cs
index a2bbfc9cd..84f3f8e36 100644
--- a/Terminal.Gui/Views/GraphView/Annotations.cs
+++ b/Terminal.Gui/Views/GraphView/Annotations.cs
@@ -114,62 +114,55 @@ namespace Terminal.Gui {
/// A box containing symbol definitions e.g. meanings for colors in a graph.
/// The 'Key' to the graph
///
- public class LegendAnnotation : IAnnotation {
-
+ public class LegendAnnotation : View, IAnnotation {
///
- /// True to draw a solid border around the legend.
- /// Defaults to true. This border will be within the
- /// and so reduces the width/height
- /// available for text by 2
- ///
- public bool Border { get; set; } = true;
-
- ///
- /// Defines the screen area available for the legend to render in
- ///
- public Rect Bounds { get; set; }
-
- ///
- /// Returns false i.e. Lengends render after series
+ /// Returns false i.e. Legends render after series
///
public bool BeforeSeries => false;
///
/// Ordered collection of entries that are rendered in the legend.
///
- List> entries = new List> ();
+ List> _entries = new List> ();
///
- /// Creates a new empty legend at the given screen coordinates
+ /// Creates a new empty legend at the empty screen coordinates.
+ ///
+ public LegendAnnotation () : this (Rect.Empty) { }
+
+ ///
+ /// Creates a new empty legend at the given screen coordinates.
///
/// Defines the area available for the legend to render in
/// (within the graph). This is in screen units (i.e. not graph space)
public LegendAnnotation (Rect legendBounds)
{
- Bounds = legendBounds;
+ X = legendBounds.X;
+ Y = legendBounds.Y;
+ Width = legendBounds.Width;
+ Height = legendBounds.Height;
+ BorderStyle = LineStyle.Single;
}
///
- /// Draws the Legend and all entries into the area within
+ /// Draws the Legend and all entries into the area within
///
///
public void Render (GraphView graph)
{
- if (Border) {
- graph.Border.DrawFrame (Bounds, true);
+ if (!IsInitialized) {
+ ColorScheme = new ColorScheme () { Normal = Application.Driver.GetAttribute () };
+ graph.Add (this);
}
- // start the legend at
- int y = Bounds.Top + (Border ? 1 : 0);
- int x = Bounds.Left + (Border ? 1 : 0);
-
- // how much horizontal space is available for writing legend entries?
- int availableWidth = Bounds.Width - (Border ? 2 : 0);
- int availableHeight = Bounds.Height - (Border ? 2 : 0);
+ if (BorderStyle != LineStyle.None) {
+ OnDrawAdornments ();
+ OnRenderLineCanvas ();
+ }
int linesDrawn = 0;
- foreach (var entry in entries) {
+ foreach (var entry in _entries) {
if (entry.Item1.Color.HasValue) {
Application.Driver.SetAttribute (entry.Item1.Color.Value);
@@ -178,35 +171,35 @@ namespace Terminal.Gui {
}
// add the symbol
- graph.AddRune (x, y + linesDrawn, entry.Item1.Rune);
+ AddRune (0, linesDrawn, entry.Item1.Rune);
// switch to normal coloring (for the text)
graph.SetDriverColorToGraphColor ();
// add the text
- graph.Move (x + 1, y + linesDrawn);
+ Move (1, linesDrawn);
- string str = TextFormatter.ClipOrPad (entry.Item2, availableWidth - 1);
+ string str = TextFormatter.ClipOrPad (entry.Item2, Bounds.Width - 1);
Application.Driver.AddStr (str);
linesDrawn++;
-
+
// Legend has run out of space
- if (linesDrawn >= availableHeight) {
+ if (linesDrawn >= Bounds.Height) {
break;
}
}
}
///
- /// Adds an entry into the legend. Duplicate entries are permissable
+ /// Adds an entry into the legend. Duplicate entries are permissible
///
/// The symbol appearing on the graph that should appear in the legend
/// Text to render on this line of the legend. Will be truncated
- /// if outside of Legend
+ /// if outside of Legend
public void AddEntry (GraphCellToRender graphCellToRender, string text)
{
- entries.Add (Tuple.Create (graphCellToRender, text));
+ _entries.Add (Tuple.Create (graphCellToRender, text));
}
}
diff --git a/Terminal.Gui/Views/GraphView/GraphView.cs b/Terminal.Gui/Views/GraphView/GraphView.cs
index 32f579b38..5253822a9 100644
--- a/Terminal.Gui/Views/GraphView/GraphView.cs
+++ b/Terminal.Gui/Views/GraphView/GraphView.cs
@@ -1,321 +1,330 @@
-using System.Text;
+#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Text;
+
+namespace Terminal.Gui;
+
+///
+/// View for rendering graphs (bar, scatter, etc...).
+///
+public class GraphView : View {
-#nullable enable
-namespace Terminal.Gui {
///
- /// Control for rendering graphs (bar, scatter etc)
+ /// Creates a new graph with a 1 to 1 graph space with absolute layout.
///
- public class GraphView : View {
+ public GraphView ()
+ {
+ CanFocus = true;
- ///
- /// Horizontal axis
- ///
- ///
- public HorizontalAxis AxisX { get; set; }
+ AxisX = new HorizontalAxis ();
+ AxisY = new VerticalAxis ();
- ///
- /// Vertical axis
- ///
- ///
- public VerticalAxis AxisY { get; set; }
+ // Things this view knows how to do
+ AddCommand (Command.ScrollUp, () => {
+ Scroll (0, CellSize.Y);
+ return true;
+ });
+ AddCommand (Command.ScrollDown, () => {
+ Scroll (0, -CellSize.Y);
+ return true;
+ });
+ AddCommand (Command.ScrollRight, () => {
+ Scroll (CellSize.X, 0);
+ return true;
+ });
+ AddCommand (Command.ScrollLeft, () => {
+ Scroll (-CellSize.X, 0);
+ return true;
+ });
+ AddCommand (Command.PageUp, () => {
+ PageUp ();
+ return true;
+ });
+ AddCommand (Command.PageDown, () => {
+ PageDown ();
+ return true;
+ });
- ///
- /// Collection of data series that are rendered in the graph
- ///
- public List Series { get; } = new List ();
+ KeyBindings.Add (KeyCode.CursorRight, Command.ScrollRight);
+ KeyBindings.Add (KeyCode.CursorLeft, Command.ScrollLeft);
+ KeyBindings.Add (KeyCode.CursorUp, Command.ScrollUp);
+ KeyBindings.Add (KeyCode.CursorDown, Command.ScrollDown);
- ///
- /// Elements drawn into graph after series have been drawn e.g. Legends etc
- ///
- public List Annotations { get; } = new List ();
-
- ///
- /// Amount of space to leave on left of control. Graph content ()
- /// will not be rendered in margins but axis labels may be
- ///
- public uint MarginLeft { get; set; }
-
- ///
- /// Amount of space to leave on bottom of control. Graph content ()
- /// will not be rendered in margins but axis labels may be
- ///
- public uint MarginBottom { get; set; }
-
- ///
- /// The graph space position of the bottom left of the control.
- /// Changing this scrolls the viewport around in the graph
- ///
- ///
- public PointF ScrollOffset { get; set; } = new PointF (0, 0);
-
- ///
- /// Translates console width/height into graph space. Defaults
- /// to 1 row/col of console space being 1 unit of graph space.
- ///
- ///
- public PointF CellSize { get; set; } = new PointF (1, 1);
-
- ///
- /// The color of the background of the graph and axis/labels
- ///
- public Attribute? GraphColor { get; set; }
-
- ///
- /// Creates a new graph with a 1 to 1 graph space with absolute layout
- ///
- public GraphView ()
- {
- CanFocus = true;
-
- AxisX = new HorizontalAxis ();
- AxisY = new VerticalAxis ();
-
- // Things this view knows how to do
- AddCommand (Command.ScrollUp, () => { Scroll (0, CellSize.Y); return true; });
- AddCommand (Command.ScrollDown, () => { Scroll (0, -CellSize.Y); return true; });
- AddCommand (Command.ScrollRight, () => { Scroll (CellSize.X, 0); return true; });
- AddCommand (Command.ScrollLeft, () => { Scroll (-CellSize.X, 0); return true; });
- AddCommand (Command.PageUp, () => { PageUp (); return true; });
- AddCommand (Command.PageDown, () => { PageDown (); return true; });
-
- KeyBindings.Add (KeyCode.CursorRight, Command.ScrollRight);
- KeyBindings.Add (KeyCode.CursorLeft, Command.ScrollLeft);
- KeyBindings.Add (KeyCode.CursorUp, Command.ScrollUp);
- KeyBindings.Add (KeyCode.CursorDown, Command.ScrollDown);
-
- // Not bound by default (preserves backwards compatibility)
- //KeyBindings.Add (Key.PageUp, Command.PageUp);
- //KeyBindings.Add (Key.PageDown, Command.PageDown);
- }
-
- ///
- /// Clears all settings configured on the graph and resets all properties
- /// to default values (, etc)
- ///
- public void Reset ()
- {
- ScrollOffset = new PointF (0, 0);
- CellSize = new PointF (1, 1);
- AxisX.Reset ();
- AxisY.Reset ();
- Series.Clear ();
- Annotations.Clear ();
- GraphColor = null;
- SetNeedsDisplay ();
- }
-
- ///
- public override void OnDrawContent (Rect contentArea)
- {
- if (CellSize.X == 0 || CellSize.Y == 0) {
- throw new Exception ($"{nameof (CellSize)} cannot be 0");
- }
-
- SetDriverColorToGraphColor ();
-
- Move (0, 0);
-
- // clear all old content
- for (int i = 0; i < Bounds.Height; i++) {
- Move (0, i);
- Driver.AddStr (new string (' ', Bounds.Width));
- }
-
- // If there is no data do not display a graph
- if (!Series.Any () && !Annotations.Any ()) {
- return;
- }
-
- // The drawable area of the graph (anything that isn't in the margins)
- var graphScreenWidth = Bounds.Width - ((int)MarginLeft);
- var graphScreenHeight = Bounds.Height - (int)MarginBottom;
-
- // if the margins take up the full draw bounds don't render
- if (graphScreenWidth < 0 || graphScreenHeight < 0) {
- return;
- }
-
- // Draw 'before' annotations
- foreach (var a in Annotations.ToArray ().Where (a => a.BeforeSeries)) {
- a.Render (this);
- }
-
- SetDriverColorToGraphColor ();
-
- AxisY.DrawAxisLine (this);
- AxisX.DrawAxisLine (this);
-
- AxisY.DrawAxisLabels (this);
- AxisX.DrawAxisLabels (this);
-
- // Draw a cross where the two axis cross
- var axisIntersection = new Point (AxisY.GetAxisXPosition (this), AxisX.GetAxisYPosition (this));
-
- if (AxisX.Visible && AxisY.Visible) {
- Move (axisIntersection.X, axisIntersection.Y);
- AddRune (axisIntersection.X, axisIntersection.Y, (Rune)'\u253C');
- }
-
- SetDriverColorToGraphColor ();
-
- Rect drawBounds = new Rect ((int)MarginLeft, 0, graphScreenWidth, graphScreenHeight);
-
- RectangleF graphSpace = ScreenToGraphSpace (drawBounds);
-
- foreach (var s in Series.ToArray ()) {
-
- s.DrawSeries (this, drawBounds, graphSpace);
-
- // If a series changes the graph color reset it
- SetDriverColorToGraphColor ();
- }
-
- SetDriverColorToGraphColor ();
-
- // Draw 'after' annotations
- foreach (var a in Annotations.ToArray ().Where (a => !a.BeforeSeries)) {
- a.Render (this);
- }
- }
-
- ///
- /// Sets the color attribute of to the
- /// (if defined) or otherwise.
- ///
- public void SetDriverColorToGraphColor ()
- {
- Driver.SetAttribute (GraphColor ?? (GetNormalColor ()));
- }
-
- ///
- /// Returns the section of the graph that is represented by the given
- /// screen position
- ///
- ///
- ///
- ///
- public RectangleF ScreenToGraphSpace (int col, int row)
- {
- return new RectangleF (
- ScrollOffset.X + ((col - MarginLeft) * CellSize.X),
- ScrollOffset.Y + ((Bounds.Height - (row + MarginBottom + 1)) * CellSize.Y),
- CellSize.X, CellSize.Y);
- }
-
- ///
- /// Returns the section of the graph that is represented by the screen area
- ///
- ///
- ///
- public RectangleF ScreenToGraphSpace (Rect screenArea)
- {
- // get position of the bottom left
- var pos = ScreenToGraphSpace (screenArea.Left, screenArea.Bottom - 1);
-
- return new RectangleF (pos.X, pos.Y, screenArea.Width * CellSize.X, screenArea.Height * CellSize.Y);
- }
- ///
- /// Calculates the screen location for a given point in graph space.
- /// Bear in mind these be off screen
- ///
- /// Point in graph space that may or may not be represented in the
- /// visible area of graph currently presented. E.g. 0,0 for origin
- /// Screen position (Column/Row) which would be used to render the graph .
- /// Note that this can be outside the current client area of the control
- public Point GraphSpaceToScreen (PointF location)
- {
- return new Point (
-
- (int)((location.X - ScrollOffset.X) / CellSize.X) + (int)MarginLeft,
- // screen coordinates are top down while graph coordinates are bottom up
- (Bounds.Height - 1) - (int)MarginBottom - (int)((location.Y - ScrollOffset.Y) / CellSize.Y)
- );
- }
-
- ///
- /// Also ensures that cursor is invisible after entering the .
- public override bool OnEnter (View view)
- {
- Driver.SetCursorVisibility (CursorVisibility.Invisible);
- return base.OnEnter (view);
- }
-
- ///
- /// Scrolls the graph up 1 page
- ///
- public void PageUp ()
- {
- Scroll (0, CellSize.Y * Bounds.Height);
- }
-
- ///
- /// Scrolls the graph down 1 page
- ///
- public void PageDown ()
- {
- Scroll (0, -1 * CellSize.Y * Bounds.Height);
- }
- ///
- /// Scrolls the view by a given number of units in graph space.
- /// See to translate this into rows/cols
- ///
- ///
- ///
- public void Scroll (float offsetX, float offsetY)
- {
- ScrollOffset = new PointF (
- ScrollOffset.X + offsetX,
- ScrollOffset.Y + offsetY);
-
- SetNeedsDisplay ();
- }
-
- #region Bresenham's line algorithm
- // https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C.23
-
- int ipart (decimal x) { return (int)x; }
-
- decimal fpart (decimal x)
- {
- if (x < 0) return (1 - (x - Math.Floor (x)));
- return (x - Math.Floor (x));
- }
-
- ///
- /// Draws a line between two points in screen space. Can be diagonals.
- ///
- ///
- ///
- /// The symbol to use for the line
- public void DrawLine (Point start, Point end, Rune symbol)
- {
- if (Equals (start, end)) {
- return;
- }
-
- int x0 = start.X;
- int y0 = start.Y;
- int x1 = end.X;
- int y1 = end.Y;
-
- int dx = Math.Abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
- int dy = Math.Abs (y1 - y0), sy = y0 < y1 ? 1 : -1;
- int err = (dx > dy ? dx : -dy) / 2, e2;
-
- while (true) {
-
- AddRune (x0, y0, symbol);
-
- if (x0 == x1 && y0 == y1) break;
- e2 = err;
- if (e2 > -dx) { err -= dy; x0 += sx; }
- if (e2 < dy) { err += dx; y0 += sy; }
- }
- }
-
- #endregion
+ // Not bound by default (preserves backwards compatibility)
+ //KeyBindings.Add (Key.PageUp, Command.PageUp);
+ //KeyBindings.Add (Key.PageDown, Command.PageDown);
}
+
+ ///
+ /// Horizontal axis.
+ ///
+ ///
+ public HorizontalAxis AxisX { get; set; }
+
+ ///
+ /// Vertical axis.
+ ///
+ ///
+ public VerticalAxis AxisY { get; set; }
+
+ ///
+ /// Collection of data series that are rendered in the graph.
+ ///
+ public List Series { get; } = new ();
+
+ ///
+ /// Elements drawn into graph after series have been drawn e.g. Legends etc.
+ ///
+ public List Annotations { get; } = new ();
+
+ ///
+ /// Amount of space to leave on left of the graph. Graph content ()
+ /// will not be rendered in margins but axis labels may be. Use to
+ /// add a margin outside of the GraphView.
+ ///
+ public uint MarginLeft { get; set; }
+
+ ///
+ /// Amount of space to leave on bottom of the graph. Graph content ()
+ /// will not be rendered in margins but axis labels may be. Use to
+ /// add a margin outside of the GraphView.
+ ///
+ public uint MarginBottom { get; set; }
+
+ ///
+ /// The graph space position of the bottom left of the graph.
+ /// Changing this scrolls the viewport around in the graph.
+ ///
+ ///
+ public PointF ScrollOffset { get; set; } = new (0, 0);
+
+ ///
+ /// Translates console width/height into graph space. Defaults
+ /// to 1 row/col of console space being 1 unit of graph space.
+ ///
+ ///
+ public PointF CellSize { get; set; } = new (1, 1);
+
+ ///
+ /// The color of the background of the graph and axis/labels.
+ ///
+ public Attribute? GraphColor { get; set; }
+
+ ///
+ /// Clears all settings configured on the graph and resets all properties
+ /// to default values (, etc) .
+ ///
+ public void Reset ()
+ {
+ ScrollOffset = new PointF (0, 0);
+ CellSize = new PointF (1, 1);
+ AxisX.Reset ();
+ AxisY.Reset ();
+ Series.Clear ();
+ Annotations.Clear ();
+ GraphColor = null;
+ SetNeedsDisplay ();
+ }
+
+ ///
+ public override void OnDrawContent (Rect contentArea)
+ {
+ if (CellSize.X == 0 || CellSize.Y == 0) {
+ throw new Exception ($"{nameof (CellSize)} cannot be 0");
+ }
+
+ SetDriverColorToGraphColor ();
+
+ Move (0, 0);
+
+ // clear all old content
+ for (var i = 0; i < Bounds.Height; i++) {
+ Move (0, i);
+ Driver.AddStr (new string (' ', Bounds.Width));
+ }
+
+ // If there is no data do not display a graph
+ if (!Series.Any () && !Annotations.Any ()) {
+ return;
+ }
+
+ // The drawable area of the graph (anything that isn't in the margins)
+ var graphScreenWidth = Bounds.Width - (int)MarginLeft;
+ var graphScreenHeight = Bounds.Height - (int)MarginBottom;
+
+ // if the margins take up the full draw bounds don't render
+ if (graphScreenWidth < 0 || graphScreenHeight < 0) {
+ return;
+ }
+
+ // Draw 'before' annotations
+ foreach (var a in Annotations.ToArray ().Where (a => a.BeforeSeries)) {
+ a.Render (this);
+ }
+
+ SetDriverColorToGraphColor ();
+
+ AxisY.DrawAxisLine (this);
+ AxisX.DrawAxisLine (this);
+
+ AxisY.DrawAxisLabels (this);
+ AxisX.DrawAxisLabels (this);
+
+ // Draw a cross where the two axis cross
+ var axisIntersection = new Point (AxisY.GetAxisXPosition (this), AxisX.GetAxisYPosition (this));
+
+ if (AxisX.Visible && AxisY.Visible) {
+ Move (axisIntersection.X, axisIntersection.Y);
+ AddRune (axisIntersection.X, axisIntersection.Y, (Rune)'\u253C');
+ }
+
+ SetDriverColorToGraphColor ();
+
+ var drawBounds = new Rect ((int)MarginLeft, 0, graphScreenWidth, graphScreenHeight);
+
+ var graphSpace = ScreenToGraphSpace (drawBounds);
+
+ foreach (var s in Series.ToArray ()) {
+
+ s.DrawSeries (this, drawBounds, graphSpace);
+
+ // If a series changes the graph color reset it
+ SetDriverColorToGraphColor ();
+ }
+
+ SetDriverColorToGraphColor ();
+
+ // Draw 'after' annotations
+ foreach (var a in Annotations.ToArray ().Where (a => !a.BeforeSeries)) {
+ a.Render (this);
+ }
+ }
+
+ ///
+ /// Sets the color attribute of to the
+ /// (if defined) or otherwise.
+ ///
+ public void SetDriverColorToGraphColor () => Driver.SetAttribute (GraphColor ?? GetNormalColor ());
+
+ ///
+ /// Returns the section of the graph that is represented by the given
+ /// screen position.
+ ///
+ ///
+ ///
+ ///
+ public RectangleF ScreenToGraphSpace (int col, int row) => new (
+ ScrollOffset.X + (col - MarginLeft) * CellSize.X,
+ ScrollOffset.Y + (Bounds.Height - (row + MarginBottom + 1)) * CellSize.Y,
+ CellSize.X, CellSize.Y);
+
+ ///
+ /// Returns the section of the graph that is represented by the screen area.
+ ///
+ ///
+ ///
+ public RectangleF ScreenToGraphSpace (Rect screenArea)
+ {
+ // get position of the bottom left
+ var pos = ScreenToGraphSpace (screenArea.Left, screenArea.Bottom - 1);
+
+ return new RectangleF (pos.X, pos.Y, screenArea.Width * CellSize.X, screenArea.Height * CellSize.Y);
+ }
+
+ ///
+ /// Calculates the screen location for a given point in graph space.
+ /// Bear in mind these may be off screen.
+ ///
+ ///
+ /// Point in graph space that may or may not be represented in the
+ /// visible area of graph currently presented. E.g. 0,0 for origin.
+ ///
+ ///
+ /// Screen position (Column/Row) which would be used to render the graph .
+ /// Note that this can be outside the current content area of the view.
+ ///
+ public Point GraphSpaceToScreen (PointF location) => new (
+ (int)((location.X - ScrollOffset.X) / CellSize.X) + (int)MarginLeft,
+ // screen coordinates are top down while graph coordinates are bottom up
+ Bounds.Height - 1 - (int)MarginBottom - (int)((location.Y - ScrollOffset.Y) / CellSize.Y)
+ );
+
+ ///
+ /// Also ensures that cursor is invisible after entering the .
+ public override bool OnEnter (View view)
+ {
+ Driver.SetCursorVisibility (CursorVisibility.Invisible);
+ return base.OnEnter (view);
+ }
+
+ ///
+ /// Scrolls the graph up 1 page.
+ ///
+ public void PageUp () => Scroll (0, CellSize.Y * Bounds.Height);
+
+ ///
+ /// Scrolls the graph down 1 page.
+ ///
+ public void PageDown () => Scroll (0, -1 * CellSize.Y * Bounds.Height);
+
+ ///
+ /// Scrolls the view by a given number of units in graph space.
+ /// See to translate this into rows/cols.
+ ///
+ ///
+ ///
+ public void Scroll (float offsetX, float offsetY)
+ {
+ ScrollOffset = new PointF (
+ ScrollOffset.X + offsetX,
+ ScrollOffset.Y + offsetY);
+
+ SetNeedsDisplay ();
+ }
+
+ #region Bresenham's line algorithm
+ // https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C.23
+
+ ///
+ /// Draws a line between two points in screen space. Can be diagonals.
+ ///
+ ///
+ ///
+ /// The symbol to use for the line
+ public void DrawLine (Point start, Point end, Rune symbol)
+ {
+ if (Equals (start, end)) {
+ return;
+ }
+
+ var x0 = start.X;
+ var y0 = start.Y;
+ var x1 = end.X;
+ var y1 = end.Y;
+
+ int dx = Math.Abs (x1 - x0), sx = x0 < x1 ? 1 : -1;
+ int dy = Math.Abs (y1 - y0), sy = y0 < y1 ? 1 : -1;
+ int err = (dx > dy ? dx : -dy) / 2, e2;
+
+ while (true) {
+
+ AddRune (x0, y0, symbol);
+
+ if (x0 == x1 && y0 == y1) {
+ break;
+ }
+ e2 = err;
+ if (e2 > -dx) {
+ err -= dy;
+ x0 += sx;
+ }
+ if (e2 < dy) {
+ err += dx;
+ y0 += sy;
+ }
+ }
+ }
+ #endregion
}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/Line.cs b/Terminal.Gui/Views/Line.cs
index ac2c3c3ff..35829fb39 100644
--- a/Terminal.Gui/Views/Line.cs
+++ b/Terminal.Gui/Views/Line.cs
@@ -23,7 +23,7 @@ namespace Terminal.Gui {
}
///
- public override bool OnDrawFrames ()
+ public override bool OnDrawAdornments ()
{
var screenBounds = BoundsToScreen (Bounds);
LineCanvas lc;
@@ -43,7 +43,7 @@ namespace Terminal.Gui {
///
public override void OnDrawContent (Rect contentArea)
{
- OnDrawFrames ();
+ OnDrawAdornments ();
}
}
}
diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs
index c5818c75e..1afdef5b7 100644
--- a/Terminal.Gui/Views/Menu/Menu.cs
+++ b/Terminal.Gui/Views/Menu/Menu.cs
@@ -633,7 +633,7 @@ class Menu : View {
Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);
Driver.SetAttribute (GetNormalColor ());
- OnDrawFrames ();
+ OnDrawAdornments ();
OnRenderLineCanvas ();
for (int i = Bounds.Y; i < _barItems.Children.Length; i++) {
diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs
index 321a48a4f..2b7245cba 100644
--- a/Terminal.Gui/Views/Menu/MenuBar.cs
+++ b/Terminal.Gui/Views/Menu/MenuBar.cs
@@ -278,7 +278,7 @@ public class MenuBar : View {
//CanFocus = true;
_selected = -1;
_selectedSub = -1;
- ColorScheme = Colors.Menu;
+ ColorScheme = Colors.ColorSchemes ["Menu"];
WantMousePositionReports = true;
IsMenuOpen = false;
diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs
index 6a4ba3e3b..4a251ae91 100644
--- a/Terminal.Gui/Views/MessageBox.cs
+++ b/Terminal.Gui/Views/MessageBox.cs
@@ -278,9 +278,9 @@ namespace Terminal.Gui {
}
if (useErrorColors) {
- d.ColorScheme = Colors.Error;
+ d.ColorScheme = Colors.ColorSchemes ["Error"];
} else {
- d.ColorScheme = Colors.Dialog;
+ d.ColorScheme = Colors.ColorSchemes ["Dialog"];
}
var messageLabel = new Label () {
@@ -303,20 +303,20 @@ namespace Terminal.Gui {
// TODO: replace with Dim.Fit when implemented
var maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds;
if (wrapMessage) {
- messageLabel.TextFormatter.Size = new Size (maxBounds.Size.Width - d.GetFramesThickness ().Horizontal, maxBounds.Size.Height - d.GetFramesThickness ().Vertical);
+ messageLabel.TextFormatter.Size = new Size (maxBounds.Size.Width - d.GetAdornmentsThickness ().Horizontal, maxBounds.Size.Height - d.GetAdornmentsThickness ().Vertical);
}
var msg = messageLabel.TextFormatter.Format ();
var messageSize = messageLabel.TextFormatter.GetFormattedSize ();
// Ensure the width fits the text + buttons
- var newWidth = Math.Max (width, Math.Max (messageSize.Width + d.GetFramesThickness ().Horizontal,
- d.GetButtonsWidth () + d.buttons.Count + d.GetFramesThickness ().Horizontal));
+ var newWidth = Math.Max (width, Math.Max (messageSize.Width + d.GetAdornmentsThickness ().Horizontal,
+ d.GetButtonsWidth () + d.buttons.Count + d.GetAdornmentsThickness ().Horizontal));
if (newWidth > d.Frame.Width) {
d.Width = newWidth;
}
// Ensure height fits the text + vspace + buttons
var lastLine = messageLabel.TextFormatter.Lines [^1];
- d.Height = Math.Max (height, messageSize.Height + (lastLine.EndsWith ("\r\n") || lastLine.EndsWith ('\n') ? 1 : 2) + d.GetFramesThickness ().Vertical);
+ d.Height = Math.Max (height, messageSize.Height + (lastLine.EndsWith ("\r\n") || lastLine.EndsWith ('\n') ? 1 : 2) + d.GetAdornmentsThickness ().Vertical);
d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame);
};
diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs
index 8ba75c672..f0249048b 100644
--- a/Terminal.Gui/Views/ProgressBar.cs
+++ b/Terminal.Gui/Views/ProgressBar.cs
@@ -82,7 +82,8 @@ public class ProgressBar : View {
void ProgressBar_LayoutStarted (object sender, EventArgs e)
{
- Bounds = new Rect (Bounds.Location, new Size (Bounds.Width, 1));
+ // TODO: use Dim.Auto
+ Height = 1 + GetAdornmentsThickness ().Vertical;
}
float _fraction;
@@ -182,9 +183,14 @@ public class ProgressBar : View {
///
public void Pulse ()
{
- if (_activityPos == null) {
+ if (_activityPos == null || _activityPos.Length == 0) {
PopulateActivityPos ();
}
+
+ if (_activityPos!.Length == 0) {
+ return;
+ }
+
if (!_isActivity) {
_isActivity = true;
_delta = 1;
@@ -192,6 +198,7 @@ public class ProgressBar : View {
for (var i = 0; i < _activityPos.Length; i++) {
_activityPos [i] += _delta;
}
+
if (_activityPos [^1] < 0) {
for (var i = 0; i < _activityPos.Length; i++) {
_activityPos [i] = i - _activityPos.Length + 2;
diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs
index e9ecfbc18..e56505e70 100644
--- a/Terminal.Gui/Views/RadioGroup.cs
+++ b/Terminal.Gui/Views/RadioGroup.cs
@@ -10,7 +10,7 @@ namespace Terminal.Gui;
public class RadioGroup : View {
int _selected = -1;
int _cursor;
- DisplayModeLayout _displayMode;
+ Orientation _orientation = Orientation.Vertical;
int _horizontalSpace = 2;
List<(int pos, int length)> _horizontal;
@@ -40,18 +40,6 @@ public class RadioGroup : View {
SetInitialProperties (radioLabels, selected);
}
- ///
- /// Initializes a new instance of the class using layout.
- /// The frame is computed from the provided radio labels.
- ///
- /// The x coordinate.
- /// The y coordinate.
- /// The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.
- /// The item to be selected, the value is clamped to the number of items.
- public RadioGroup (int x, int y, string [] radioLabels, int selected = 0) :
- this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
- { }
-
void SetInitialProperties (string [] radioLabels, int selected)
{
HotKeySpecifier = new Rune ('_');
@@ -86,26 +74,42 @@ public class RadioGroup : View {
}
///
- /// Gets or sets the for this .
+ /// Gets or sets the for this . The default is .
///
- public DisplayModeLayout DisplayMode {
- get { return _displayMode; }
- set {
- if (_displayMode != value) {
- _displayMode = value;
- SetWidthHeight (_radioLabels);
- SetNeedsDisplay ();
- }
- }
+ public Orientation Orientation {
+ get => _orientation;
+ set => OnOrientationChanged (value);
}
///
- /// Gets or sets the horizontal space for this if the is
+ /// Fired when the view orientation has changed. Can be cancelled by setting
+ /// to true.
+ ///
+ public event EventHandler OrientationChanged;
+
+ ///
+ /// Called when the view orientation has changed. Invokes the event.
+ ///
+ ///
+ /// True of the event was cancelled.
+ public virtual bool OnOrientationChanged (Orientation newOrientation)
+ {
+ var args = new OrientationEventArgs (newOrientation);
+ OrientationChanged?.Invoke (this, args);
+ if (!args.Cancel) {
+ _orientation = newOrientation;
+ SetNeedsLayout ();
+ }
+ return args.Cancel;
+ }
+
+ ///
+ /// Gets or sets the horizontal space for this if the is
///
public int HorizontalSpace {
get { return _horizontalSpace; }
set {
- if (_horizontalSpace != value && _displayMode == DisplayModeLayout.Horizontal) {
+ if (_horizontalSpace != value && _orientation == Orientation.Horizontal) {
_horizontalSpace = value;
SetWidthHeight (_radioLabels);
UpdateTextFormatterText ();
@@ -116,24 +120,24 @@ public class RadioGroup : View {
void SetWidthHeight (List radioLabels)
{
- switch (_displayMode) {
- case DisplayModeLayout.Vertical:
+ switch (_orientation) {
+ case Orientation.Vertical:
var r = MakeRect (0, 0, radioLabels);
- Bounds = new Rect (Bounds.Location, new Size (r.Width, radioLabels.Count));
+ if (IsInitialized) {
+ Width = r.Width + GetAdornmentsThickness ().Horizontal;
+ Height = radioLabels.Count + GetAdornmentsThickness ().Vertical;
+ }
break;
- case DisplayModeLayout.Horizontal:
+ case Orientation.Horizontal:
CalculateHorizontalPositions ();
var length = 0;
foreach (var item in _horizontal) {
length += item.length;
}
- var hr = new Rect (0, 0, length, 1);
- if (IsAdded) {
- Width = hr.Width;
- Height = 1;
- } else {
- Bounds = new Rect (Bounds.Location, new Size (hr.Width, radioLabels.Count));
+ if (IsInitialized) {
+ Width = length + GetAdornmentsThickness ().Vertical;
+ Height = 1 + GetAdornmentsThickness ().Horizontal;
}
break;
}
@@ -198,7 +202,7 @@ public class RadioGroup : View {
if (KeyBindings.TryGet (key, out _)) {
// Search RadioLabels
for (int i = 0; i < _radioLabels.Count; i++) {
- if (TextFormatter.FindHotKey (_radioLabels [i], HotKeySpecifier, true, out _, out var hotKey)
+ if (TextFormatter.FindHotKey (_radioLabels [i], HotKeySpecifier, true, out _, out var hotKey)
&& (key.NoAlt.NoCtrl.NoShift) == hotKey) {
SelectedItem = i;
keyEvent.Scope = KeyBindingScope.HotKey;
@@ -212,7 +216,7 @@ public class RadioGroup : View {
void CalculateHorizontalPositions ()
{
- if (_displayMode == DisplayModeLayout.Horizontal) {
+ if (_orientation == Orientation.Horizontal) {
_horizontal = new List<(int pos, int length)> ();
int start = 0;
int length = 0;
@@ -231,11 +235,11 @@ public class RadioGroup : View {
Driver.SetAttribute (GetNormalColor ());
for (int i = 0; i < _radioLabels.Count; i++) {
- switch (DisplayMode) {
- case DisplayModeLayout.Vertical:
+ switch (Orientation) {
+ case Orientation.Vertical:
Move (0, i);
break;
- case DisplayModeLayout.Horizontal:
+ case Orientation.Horizontal:
Move (_horizontal [i].pos, 0);
break;
}
@@ -275,11 +279,11 @@ public class RadioGroup : View {
///
public override void PositionCursor ()
{
- switch (DisplayMode) {
- case DisplayModeLayout.Vertical:
+ switch (Orientation) {
+ case Orientation.Vertical:
Move (0, _cursor);
break;
- case DisplayModeLayout.Horizontal:
+ case Orientation.Horizontal:
Move (_horizontal [_cursor].pos, 0);
break;
}
@@ -373,11 +377,11 @@ public class RadioGroup : View {
int boundsX = me.X;
int boundsY = me.Y;
- var pos = _displayMode == DisplayModeLayout.Horizontal ? boundsX : boundsY;
- var rCount = _displayMode == DisplayModeLayout.Horizontal ? _horizontal.Last ().pos + _horizontal.Last ().length : _radioLabels.Count;
+ var pos = _orientation == Orientation.Horizontal ? boundsX : boundsY;
+ var rCount = _orientation == Orientation.Horizontal ? _horizontal.Last ().pos + _horizontal.Last ().length : _radioLabels.Count;
if (pos < rCount) {
- var c = _displayMode == DisplayModeLayout.Horizontal ? _horizontal.FindIndex ((x) => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX) : boundsY;
+ var c = _orientation == Orientation.Horizontal ? _horizontal.FindIndex ((x) => x.pos <= boundsX && x.pos + x.length - 2 >= boundsX) : boundsY;
if (c > -1) {
_cursor = SelectedItem = c;
SetNeedsDisplay ();
@@ -395,16 +399,3 @@ public class RadioGroup : View {
}
}
-///
-/// Used for choose the display mode of this
-///
-public enum DisplayModeLayout {
- ///
- /// Vertical mode display. It's the default.
- ///
- Vertical,
- ///
- /// Horizontal mode display.
- ///
- Horizontal
-}
diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs
index af1759b8f..2c3785aab 100644
--- a/Terminal.Gui/Views/Slider.cs
+++ b/Terminal.Gui/Views/Slider.cs
@@ -808,12 +808,12 @@ public class Slider : View {
Height = 0;
if (_config._sliderOrientation == Orientation.Horizontal) {
Bounds = new Rect (Bounds.Location,
- new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcBestLength ()),
- int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical, CalcThickness ())));
+ new Size (int.Min (SuperView.Bounds.Width - GetAdornmentsThickness ().Horizontal, CalcBestLength ()),
+ int.Min (SuperView.Bounds.Height - GetAdornmentsThickness ().Vertical, CalcThickness ())));
} else {
Bounds = new Rect (Bounds.Location,
- new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcThickness ()),
- int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical, CalcBestLength ())));
+ new Size (int.Min (SuperView.Bounds.Width - GetAdornmentsThickness ().Horizontal, CalcThickness ()),
+ int.Min (SuperView.Bounds.Height - GetAdornmentsThickness ().Vertical, CalcBestLength ())));
}
}
@@ -1047,7 +1047,7 @@ public class Slider : View {
// Attributes
var normalAttr = new Attribute (Color.White, Color.Black);
- var setAtrr = new Attribute (Color.Black, Color.White);
+ var setAtrr = new Attribute (Color.Black, Color.White);
if (IsInitialized) {
normalAttr = ColorScheme?.Normal ?? Application.Current.ColorScheme.Normal;
setAtrr = Style.SetChar.Attribute ?? ColorScheme.HotNormal;
@@ -1187,7 +1187,7 @@ public class Slider : View {
{
// Attributes
var normalAttr = new Attribute (Color.White, Color.Black);
- var setAttr = new Attribute (Color.Black, Color.White);
+ var setAttr = new Attribute (Color.Black, Color.White);
var spaceAttr = normalAttr;
if (IsInitialized) {
normalAttr = Style.LegendAttributes.NormalAttribute ?? ColorScheme?.Normal ?? ColorScheme.Disabled;
@@ -1459,15 +1459,15 @@ public class Slider : View {
void SetCommands ()
{
- AddCommand (Command.Right, () => MovePlus ());
- AddCommand (Command.LineDown, () => MovePlus ());
- AddCommand (Command.Left, () => MoveMinus ());
- AddCommand (Command.LineUp, () => MoveMinus ());
- AddCommand (Command.LeftHome, () => MoveStart ());
- AddCommand (Command.RightEnd, () => MoveEnd ());
+ AddCommand (Command.Right, () => MovePlus ());
+ AddCommand (Command.LineDown, () => MovePlus ());
+ AddCommand (Command.Left, () => MoveMinus ());
+ AddCommand (Command.LineUp, () => MoveMinus ());
+ AddCommand (Command.LeftHome, () => MoveStart ());
+ AddCommand (Command.RightEnd, () => MoveEnd ());
AddCommand (Command.RightExtend, () => ExtendPlus ());
- AddCommand (Command.LeftExtend, () => ExtendMinus ());
- AddCommand (Command.Accept, () => Set ());
+ AddCommand (Command.LeftExtend, () => ExtendMinus ());
+ AddCommand (Command.Accept, () => Set ());
SetKeyBindings ();
}
@@ -1497,8 +1497,8 @@ public class Slider : View {
KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.LeftExtend);
}
- KeyBindings.Add (KeyCode.Home, Command.LeftHome);
- KeyBindings.Add (KeyCode.End, Command.RightEnd);
+ KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+ KeyBindings.Add (KeyCode.End, Command.RightEnd);
KeyBindings.Add (KeyCode.Enter, Command.Accept);
KeyBindings.Add (KeyCode.Space, Command.Accept);
diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs
index 4fdd0b6c9..1c5020226 100644
--- a/Terminal.Gui/Views/StatusBar.cs
+++ b/Terminal.Gui/Views/StatusBar.cs
@@ -111,7 +111,7 @@ public class StatusBar : View {
Items = items;
}
CanFocus = false;
- ColorScheme = Colors.Menu;
+ ColorScheme = Colors.ColorSchemes ["Menu"];
X = 0;
Y = Pos.AnchorEnd (1);
Width = Dim.Fill ();
diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs
index f8354eee4..4d736c78d 100644
--- a/Terminal.Gui/Views/TextField.cs
+++ b/Terminal.Gui/Views/TextField.cs
@@ -1,1375 +1,1454 @@
-//
-// TextField.cs: single-line text editor with Emacs keybindings
-//
-// Authors:
-// Miguel de Icaza (miguel@gnome.org)
-//
-
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Threading;
using System.Text;
+using System.Threading;
using Terminal.Gui.Resources;
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+///
+/// Single-line text entry
+///
+///
+/// The provides editing functionality and mouse support.
+///
+public class TextField : View {
+ CultureInfo _currentCulture;
+
+ CursorVisibility _desiredCursorVisibility = CursorVisibility.Default;
+ int _cursorPosition;
+ readonly HistoryText _historyText = new ();
+ bool _isButtonPressed;
+ bool _isButtonReleased = true;
+
+ bool _isDrawing;
+
+ int _preTextChangedCursorPos;
+
+ CursorVisibility _savedCursorVisibility;
+ int _selectedStart = -1; // -1 represents there is no text selection.
+ string _selectedText;
+
+ int _start;
+ List _text;
+
+ CursorVisibility _visibility;
+
///
- /// Single-line text entry
+ /// Initializes a new instance of the class using positioning.
///
- ///
- /// The provides editing functionality and mouse support.
- ///
- public class TextField : View {
- List _text;
- int _first, _cursorPosition;
- int _selectedStart = -1; // -1 represents there is no text selection.
- string _selectedText;
- HistoryText _historyText = new HistoryText ();
- CultureInfo _currentCulture;
+ public TextField () : this (string.Empty) { }
- ///
- /// Gets or sets the text to render in control when no value has
- /// been entered yet and the does not yet have
- /// input focus.
- ///
- public string Caption { get; set; }
+ ///
+ /// Initializes a new instance of the class using positioning.
+ ///
+ /// Initial text contents.
+ public TextField (string text) : base (text) => SetInitialProperties (text, text.GetRuneCount () + 1);
- ///
- /// Gets or sets the foreground to use when
- /// rendering .
- ///
- public Color CaptionColor { get; set; } = new Color (Color.DarkGray);
+ ///
+ /// Initializes a new instance of the class using positioning.
+ ///
+ /// The x coordinate.
+ /// The y coordinate.
+ /// The width.
+ /// Initial text contents.
+ public TextField (int x, int y, int w, string text) : base (new Rect (x, y, w, 1)) => SetInitialProperties (text, w);
- ///
- /// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input should be appended at the cursor position, rather than clearing the entry
- ///
- public bool Used { get; set; }
+ ///
+ /// Gets or sets the text to render in control when no value has
+ /// been entered yet and the does not yet have
+ /// input focus.
+ ///
+ public string Caption { get; set; }
- ///
- /// If set to true its not allow any changes in the text.
- ///
- public bool ReadOnly { get; set; } = false;
+ ///
+ /// Gets or sets the foreground to use when
+ /// rendering .
+ ///
+ public Color CaptionColor { get; set; } = new (Color.DarkGray);
- ///
- /// Changing event, raised before the changes and can be canceled or changing the new text.
- ///
- public event EventHandler TextChanging;
+ ///
+ /// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input
+ /// should be appended at the cursor position, rather than clearing the entry
+ ///
+ public bool Used { get; set; }
- ///
- /// Changed event, raised when the text has changed.
- ///
- /// This event is raised when the changes.
- /// The passed is a containing the old value.
- ///
- ///
- public event EventHandler TextChanged;
+ ///
+ /// If set to true its not allow any changes in the text.
+ ///
+ public bool ReadOnly { get; set; } = false;
- ///
- /// Initializes a new instance of the class using positioning.
- ///
- public TextField () : this (string.Empty) { }
+ ///
+ /// Provides autocomplete context menu based on suggestions at the current cursor
+ /// position. Configure to enable this feature.
+ ///
+ public IAutocomplete Autocomplete { get; set; } = new TextFieldAutocomplete ();
- ///
- /// Initializes a new instance of the class using positioning.
- ///
- /// Initial text contents.
- public TextField (string text) : base (text)
- {
- SetInitialProperties (text, text.GetRuneCount () + 1);
- }
+ ///
+ /// Sets or gets the text held by the view.
+ ///
+ public new string Text {
+ get => StringExtensions.ToString (_text);
+ set {
+ var oldText = StringExtensions.ToString (_text);
- ///
- /// Initializes a new instance of the class using positioning.
- ///
- /// The x coordinate.
- /// The y coordinate.
- /// The width.
- /// Initial text contents.
- public TextField (int x, int y, int w, string text) : base (new Rect (x, y, w, 1))
- {
- SetInitialProperties (text, w);
- }
-
- void SetInitialProperties (string text, int w)
- {
- Height = 1;
-
- if (text == null)
- text = "";
-
- this._text = text.Split ("\n") [0].EnumerateRunes ().ToList ();
- _cursorPosition = text.GetRuneCount ();
- _first = _cursorPosition > w + 1 ? _cursorPosition - w + 1 : 0;
- CanFocus = true;
- Used = true;
- WantMousePositionReports = true;
- _savedCursorVisibility = _desiredCursorVisibility;
-
- _historyText.ChangeText += HistoryText_ChangeText;
-
- Initialized += TextField_Initialized;
-
- LayoutComplete += TextField_LayoutComplete;
-
- // Things this view knows how to do
- AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
- AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
- AddCommand (Command.LeftHomeExtend, () => { MoveHomeExtend (); return true; });
- AddCommand (Command.RightEndExtend, () => { MoveEndExtend (); return true; });
- AddCommand (Command.LeftHome, () => { MoveHome (); return true; });
- AddCommand (Command.LeftExtend, () => { MoveLeftExtend (); return true; });
- AddCommand (Command.RightExtend, () => { MoveRightExtend (); return true; });
- AddCommand (Command.WordLeftExtend, () => { MoveWordLeftExtend (); return true; });
- AddCommand (Command.WordRightExtend, () => { MoveWordRightExtend (); return true; });
- AddCommand (Command.Left, () => { MoveLeft (); return true; });
- AddCommand (Command.RightEnd, () => { MoveEnd (); return true; });
- AddCommand (Command.Right, () => { MoveRight (); return true; });
- AddCommand (Command.CutToEndLine, () => { KillToEnd (); return true; });
- AddCommand (Command.CutToStartLine, () => { KillToStart (); return true; });
- AddCommand (Command.Undo, () => { Undo (); return true; });
- AddCommand (Command.Redo, () => { Redo (); return true; });
- AddCommand (Command.WordLeft, () => { MoveWordLeft (); return true; });
- AddCommand (Command.WordRight, () => { MoveWordRight (); return true; });
- AddCommand (Command.KillWordForwards, () => { KillWordForwards (); return true; });
- AddCommand (Command.KillWordBackwards, () => { KillWordBackwards (); return true; });
- AddCommand (Command.ToggleOverwrite, () => { SetOverwrite (!Used); return true; });
- AddCommand (Command.EnableOverwrite, () => { SetOverwrite (true); return true; });
- AddCommand (Command.DisableOverwrite, () => { SetOverwrite (false); return true; });
- AddCommand (Command.Copy, () => { Copy (); return true; });
- AddCommand (Command.Cut, () => { Cut (); return true; });
- AddCommand (Command.Paste, () => { Paste (); return true; });
- AddCommand (Command.SelectAll, () => { SelectAll (); return true; });
- AddCommand (Command.DeleteAll, () => { DeleteAll (); return true; });
- AddCommand (Command.ShowContextMenu, () => { ShowContextMenu (); return true; });
-
- // Default keybindings for this view
- // We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
- KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
- KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
-
- KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
-
- KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend);
- KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
- KeyBindings.Add (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
-
- KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.RightEndExtend);
- KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
- KeyBindings.Add (KeyCode.E | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
-
- KeyBindings.Add (KeyCode.Home, Command.LeftHome);
- KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.LeftHome);
- KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome);
-
- KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
- KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LeftExtend);
-
- KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
- KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.RightExtend);
-
- KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
- KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
- KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordLeftExtend);
-
- KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
- KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
- KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.ShiftMask | KeyCode.AltMask), Command.WordRightExtend);
-
- KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
- KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
-
- KeyBindings.Add (KeyCode.End, Command.RightEnd);
- KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.RightEnd);
- KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd);
-
- KeyBindings.Add (KeyCode.CursorRight, Command.Right);
- KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
-
- KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine);
- KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine);
-
- KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo);
- KeyBindings.Add (KeyCode.Backspace | KeyCode.AltMask, Command.Undo);
-
- KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Redo);
-
- KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft);
- KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.WordLeft);
- KeyBindings.Add ((KeyCode)((int)'B' + KeyCode.AltMask), Command.WordLeft);
-
- KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight);
- KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight);
- KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight);
-
- KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards);
- KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards);
- KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite);
- KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
- KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
- KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste);
- KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
-
- KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.DeleteAll);
- KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll);
-
- _currentCulture = Thread.CurrentThread.CurrentUICulture;
-
- ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ());
- ContextMenu.KeyChanged += ContextMenu_KeyChanged;
-
- KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu);
- }
-
- private void TextField_LayoutComplete (object sender, LayoutEventArgs e)
- {
- // Don't let height > 1
- if (Frame.Height > 1) {
- Height = 1;
- }
- }
-
-
- private MenuBarItem BuildContextMenuBarItem ()
- {
- return new MenuBarItem (new MenuItem [] {
- new MenuItem (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)),
- new MenuItem (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)),
- new MenuItem (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)),
- new MenuItem (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)),
- new MenuItem (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)),
- new MenuItem (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)),
- new MenuItem (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)),
- });
- }
-
- private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
- {
- KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode);
- }
-
- private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj)
- {
- if (obj == null)
+ if (oldText == value) {
return;
-
- Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
- CursorPosition = obj.CursorPosition.X;
- Adjust ();
- }
-
- void TextField_Initialized (object sender, EventArgs e)
- {
- Autocomplete.HostControl = this;
- Autocomplete.PopupInsideContainer = false;
- }
-
- ///
- public override bool OnEnter (View view)
- {
- if (IsInitialized) {
- Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
}
- return base.OnEnter (view);
- }
-
- ///
- public override bool OnLeave (View view)
- {
- if (Application.MouseGrabView != null && Application.MouseGrabView == this)
- Application.UngrabMouse ();
- //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
- // ClearAllSelection ();
-
- return base.OnLeave (view);
- }
-
- ///
- /// Provides autocomplete context menu based on suggestions at the current cursor
- /// position. Configure to enable this feature.
- ///
- public IAutocomplete Autocomplete { get; set; } = new TextFieldAutocomplete ();
-
- ///
- /// Sets or gets the text held by the view.
- ///
- public new string Text {
- get {
- return StringExtensions.ToString (_text);
- }
-
- set {
- var oldText = StringExtensions.ToString (_text);
-
- if (oldText == value)
- return;
-
- var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]);
- if (newText.Cancel) {
- if (_cursorPosition > _text.Count) {
- _cursorPosition = _text.Count;
- }
- return;
- }
- ClearAllSelection ();
- _text = newText.NewText.EnumerateRunes ().ToList ();
-
- if (!Secret && !_historyText.IsFromHistory) {
- _historyText.Add (new List> () { TextModel.ToRuneCellList (oldText) },
- new Point (_cursorPosition, 0));
- _historyText.Add (new List> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)
- , HistoryText.LineStatus.Replaced);
- }
-
- TextChanged?.Invoke (this, new TextChangedEventArgs (oldText));
-
- ProcessAutocomplete ();
-
+ var newText = OnTextChanging (value.Replace ("\t", "").Split ("\n") [0]);
+ if (newText.Cancel) {
if (_cursorPosition > _text.Count) {
- _cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
- }
-
- Adjust ();
- SetNeedsDisplay ();
- }
- }
-
- ///
- /// Sets the secret property.
- ///
- /// This makes the text entry suitable for entering passwords.
- ///
- ///
- public bool Secret { get; set; }
-
- ///
- /// Sets or gets the current cursor position.
- ///
- public virtual int CursorPosition {
- get { return _cursorPosition; }
- set {
- if (value < 0) {
- _cursorPosition = 0;
- } else if (value > _text.Count) {
_cursorPosition = _text.Count;
- } else {
- _cursorPosition = value;
}
- PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
- }
- }
-
- ///
- /// Gets the left offset position.
- ///
- public int ScrollOffset => _first;
-
- ///
- /// Indicates whatever the text was changed or not.
- /// if the text was changed otherwise.
- ///
- public bool IsDirty => _historyText.IsDirty (Text);
-
- ///
- /// Indicates whatever the text has history changes or not.
- /// if the text has history changes otherwise.
- ///
- public bool HasHistoryChanges => _historyText.HasHistoryChanges;
-
- ///
- /// Get the for this view.
- ///
- public ContextMenu ContextMenu { get; private set; }
-
- ///
- /// Sets the cursor position.
- ///
- public override void PositionCursor ()
- {
- if (!IsInitialized) {
return;
}
+ ClearAllSelection ();
+ _text = newText.NewText.EnumerateRunes ().ToList ();
+
+ if (!Secret && !_historyText.IsFromHistory) {
+ _historyText.Add (new List> { TextModel.ToRuneCellList (oldText) },
+ new Point (_cursorPosition, 0));
+ _historyText.Add (new List> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0)
+ , HistoryText.LineStatus.Replaced);
+ }
+
+ TextChanged?.Invoke (this, new TextChangedEventArgs (oldText));
+
ProcessAutocomplete ();
- var col = 0;
- for (int idx = _first < 0 ? 0 : _first; idx < _text.Count; idx++) {
- if (idx == _cursorPosition)
- break;
- var cols = _text [idx].GetColumns ();
- TextModel.SetCol (ref col, Frame.Width - 1, cols);
+ if (_cursorPosition > _text.Count) {
+ _cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
}
- var pos = _cursorPosition - _first + Math.Min (Frame.X, 0);
- var offB = OffSetBackground ();
- var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default;
- var thisFrame = BoundsToScreen (Bounds);
- if (pos > -1 && col >= pos && pos < Frame.Width + offB
- && containerFrame.IntersectsWith (thisFrame)) {
- RestoreCursorVisibility ();
- Move (col, 0);
+
+ Adjust ();
+ SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// Sets the secret property.
+ ///
+ /// This makes the text entry suitable for entering passwords.
+ ///
+ ///
+ public bool Secret { get; set; }
+
+ ///
+ /// Sets or gets the current cursor position.
+ ///
+ public virtual int CursorPosition {
+ get => _cursorPosition;
+ set {
+ if (value < 0) {
+ _cursorPosition = 0;
+ } else if (value > _text.Count) {
+ _cursorPosition = _text.Count;
} else {
- HideCursorVisibility ();
- if (pos < 0) {
- Move (pos, 0);
- } else {
- Move (pos - offB, 0);
- }
+ _cursorPosition = value;
+ }
+ PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
+ }
+ }
+
+ ///
+ /// Gets the left offset position.
+ ///
+ public int ScrollOffset { get; private set; }
+
+ ///
+ /// Indicates whatever the text was changed or not.
+ /// if the text was changed otherwise.
+ ///
+ public bool IsDirty => _historyText.IsDirty (Text);
+
+ ///
+ /// Indicates whatever the text has history changes or not.
+ /// if the text has history changes otherwise.
+ ///
+ public bool HasHistoryChanges => _historyText.HasHistoryChanges;
+
+ ///
+ /// Get the for this view.
+ ///
+ public ContextMenu ContextMenu { get; private set; }
+
+ ///
+ public override bool CanFocus {
+ get => base.CanFocus;
+ set => base.CanFocus = value;
+ }
+
+ ///
+ /// Start position of the selected text.
+ ///
+ public int SelectedStart {
+ get => _selectedStart;
+ set {
+ if (value < -1) {
+ _selectedStart = -1;
+ } else if (value > _text.Count) {
+ _selectedStart = _text.Count;
+ } else {
+ _selectedStart = value;
+ }
+ PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
+ }
+ }
+
+ ///
+ /// Length of the selected text.
+ ///
+ public int SelectedLength { get; private set; }
+
+ ///
+ /// The selected text.
+ ///
+ public string SelectedText {
+ get => Secret ? null : _selectedText;
+ private set => _selectedText = value;
+ }
+
+ ///
+ /// Get / Set the wished cursor when the field is focused
+ ///
+ public CursorVisibility DesiredCursorVisibility {
+ get => _desiredCursorVisibility;
+ set {
+ if ((_desiredCursorVisibility != value || _visibility != value) && HasFocus) {
+ Application.Driver.SetCursorVisibility (value);
+ }
+
+ _desiredCursorVisibility = _visibility = value;
+ }
+ }
+
+ ///
+ /// Changing event, raised before the changes and can be canceled or changing the new text.
+ ///
+ public event EventHandler TextChanging;
+
+ ///
+ /// Changed event, raised when the text has changed.
+ ///
+ /// This event is raised when the changes.
+ /// The passed is a containing the old value.
+ ///
+ ///
+ public event EventHandler TextChanged;
+
+ void SetInitialProperties (string text, int w)
+ {
+ Height = 1;
+
+ if (text == null) {
+ text = "";
+ }
+
+ _text = text.Split ("\n") [0].EnumerateRunes ().ToList ();
+ _cursorPosition = text.GetRuneCount ();
+ ScrollOffset = _cursorPosition > w + 1 ? _cursorPosition - w + 1 : 0;
+ CanFocus = true;
+ Used = true;
+ WantMousePositionReports = true;
+ _savedCursorVisibility = _desiredCursorVisibility;
+
+ _historyText.ChangeText += HistoryText_ChangeText;
+
+ Initialized += TextField_Initialized;
+
+ LayoutComplete += TextField_LayoutComplete;
+
+ // Things this view knows how to do
+ AddCommand (Command.DeleteCharRight, () => {
+ DeleteCharRight ();
+ return true;
+ });
+ AddCommand (Command.DeleteCharLeft, () => {
+ DeleteCharLeft (false);
+ return true;
+ });
+ AddCommand (Command.LeftHomeExtend, () => {
+ MoveHomeExtend ();
+ return true;
+ });
+ AddCommand (Command.RightEndExtend, () => {
+ MoveEndExtend ();
+ return true;
+ });
+ AddCommand (Command.LeftHome, () => {
+ MoveHome ();
+ return true;
+ });
+ AddCommand (Command.LeftExtend, () => {
+ MoveLeftExtend ();
+ return true;
+ });
+ AddCommand (Command.RightExtend, () => {
+ MoveRightExtend ();
+ return true;
+ });
+ AddCommand (Command.WordLeftExtend, () => {
+ MoveWordLeftExtend ();
+ return true;
+ });
+ AddCommand (Command.WordRightExtend, () => {
+ MoveWordRightExtend ();
+ return true;
+ });
+ AddCommand (Command.Left, () => {
+ MoveLeft ();
+ return true;
+ });
+ AddCommand (Command.RightEnd, () => {
+ MoveEnd ();
+ return true;
+ });
+ AddCommand (Command.Right, () => {
+ MoveRight ();
+ return true;
+ });
+ AddCommand (Command.CutToEndLine, () => {
+ KillToEnd ();
+ return true;
+ });
+ AddCommand (Command.CutToStartLine, () => {
+ KillToStart ();
+ return true;
+ });
+ AddCommand (Command.Undo, () => {
+ Undo ();
+ return true;
+ });
+ AddCommand (Command.Redo, () => {
+ Redo ();
+ return true;
+ });
+ AddCommand (Command.WordLeft, () => {
+ MoveWordLeft ();
+ return true;
+ });
+ AddCommand (Command.WordRight, () => {
+ MoveWordRight ();
+ return true;
+ });
+ AddCommand (Command.KillWordForwards, () => {
+ KillWordForwards ();
+ return true;
+ });
+ AddCommand (Command.KillWordBackwards, () => {
+ KillWordBackwards ();
+ return true;
+ });
+ AddCommand (Command.ToggleOverwrite, () => {
+ SetOverwrite (!Used);
+ return true;
+ });
+ AddCommand (Command.EnableOverwrite, () => {
+ SetOverwrite (true);
+ return true;
+ });
+ AddCommand (Command.DisableOverwrite, () => {
+ SetOverwrite (false);
+ return true;
+ });
+ AddCommand (Command.Copy, () => {
+ Copy ();
+ return true;
+ });
+ AddCommand (Command.Cut, () => {
+ Cut ();
+ return true;
+ });
+ AddCommand (Command.Paste, () => {
+ Paste ();
+ return true;
+ });
+ AddCommand (Command.SelectAll, () => {
+ SelectAll ();
+ return true;
+ });
+ AddCommand (Command.DeleteAll, () => {
+ DeleteAll ();
+ return true;
+ });
+ AddCommand (Command.ShowContextMenu, () => {
+ ShowContextMenu ();
+ return true;
+ });
+
+ // Default keybindings for this view
+ // We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
+ KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
+ KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
+
+ KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
+
+ KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.LeftHomeExtend);
+ KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
+ KeyBindings.Add (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.LeftHomeExtend);
+
+ KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.RightEndExtend);
+ KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
+ KeyBindings.Add (KeyCode.E | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.RightEndExtend);
+
+ KeyBindings.Add (KeyCode.Home, Command.LeftHome);
+ KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.LeftHome);
+ KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome);
+
+ KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
+ KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LeftExtend);
+
+ KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
+ KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.RightExtend);
+
+ KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
+ KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordLeftExtend);
+ KeyBindings.Add ('B' + KeyCode.ShiftMask | KeyCode.AltMask, Command.WordLeftExtend);
+
+ KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
+ KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.WordRightExtend);
+ KeyBindings.Add ('F' + KeyCode.ShiftMask | KeyCode.AltMask, Command.WordRightExtend);
+
+ KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+ KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
+
+ KeyBindings.Add (KeyCode.End, Command.RightEnd);
+ KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.RightEnd);
+ KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd);
+
+ KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+ KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
+
+ KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine);
+ KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine);
+
+ KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo);
+ KeyBindings.Add (KeyCode.Backspace | KeyCode.AltMask, Command.Undo);
+
+ KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Redo);
+
+ KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft);
+ KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.WordLeft);
+ KeyBindings.Add ('B' + KeyCode.AltMask, Command.WordLeft);
+
+ KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight);
+ KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight);
+ KeyBindings.Add ('F' + KeyCode.AltMask, Command.WordRight);
+
+ KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards);
+ KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards);
+ KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite);
+ KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
+ KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
+ KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.Paste);
+ KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
+
+ KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.DeleteAll);
+ KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll);
+
+ _currentCulture = Thread.CurrentThread.CurrentUICulture;
+
+ ContextMenu = new ContextMenu (this, BuildContextMenuBarItem ());
+ ContextMenu.KeyChanged += ContextMenu_KeyChanged;
+
+ KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu);
+ }
+
+ void TextField_LayoutComplete (object sender, LayoutEventArgs e)
+ {
+ // Don't let height > 1
+ if (Frame.Height > 1) {
+ Height = 1;
+ }
+ }
+
+
+ MenuBarItem BuildContextMenuBarItem () => new (new MenuItem [] {
+ new (Strings.ctxSelectAll, "", () => SelectAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)),
+ new (Strings.ctxDeleteAll, "", () => DeleteAll (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)),
+ new (Strings.ctxCopy, "", () => Copy (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)),
+ new (Strings.ctxCut, "", () => Cut (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)),
+ new (Strings.ctxPaste, "", () => Paste (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)),
+ new (Strings.ctxUndo, "", () => Undo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)),
+ new (Strings.ctxRedo, "", () => Redo (), null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo))
+ });
+
+ void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) => KeyBindings.Replace (e.OldKey.KeyCode, e.NewKey.KeyCode);
+
+ void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItem obj)
+ {
+ if (obj == null) {
+ return;
+ }
+
+ Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
+ CursorPosition = obj.CursorPosition.X;
+ Adjust ();
+ }
+
+ void TextField_Initialized (object sender, EventArgs e)
+ {
+ Autocomplete.HostControl = this;
+ Autocomplete.PopupInsideContainer = false;
+ }
+
+ ///
+ public override bool OnEnter (View view)
+ {
+ if (IsInitialized) {
+ Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
+ }
+
+ return base.OnEnter (view);
+ }
+
+ ///
+ public override bool OnLeave (View view)
+ {
+ if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
+ Application.UngrabMouse ();
+ }
+ //if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
+ // ClearAllSelection ();
+
+ return base.OnLeave (view);
+ }
+
+ ///
+ /// Sets the cursor position.
+ ///
+ public override void PositionCursor ()
+ {
+ if (!IsInitialized) {
+ return;
+ }
+ ProcessAutocomplete ();
+
+ var col = 0;
+ for (var idx = ScrollOffset < 0 ? 0 : ScrollOffset; idx < _text.Count; idx++) {
+ if (idx == _cursorPosition) {
+ break;
+ }
+ var cols = _text [idx].GetColumns ();
+ TextModel.SetCol (ref col, Frame.Width - 1, cols);
+ }
+ var pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
+ var offB = OffSetBackground ();
+ var containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default;
+ var thisFrame = BoundsToScreen (Bounds);
+ if (pos > -1 && col >= pos && pos < Frame.Width + offB
+ && containerFrame.IntersectsWith (thisFrame)) {
+ RestoreCursorVisibility ();
+ Move (col, 0);
+ } else {
+ HideCursorVisibility ();
+ if (pos < 0) {
+ Move (pos, 0);
+ } else {
+ Move (pos - offB, 0);
+ }
+ }
+ }
+
+ void HideCursorVisibility ()
+ {
+ if (_desiredCursorVisibility != CursorVisibility.Invisible) {
+ DesiredCursorVisibility = CursorVisibility.Invisible;
+ }
+ }
+
+ void RestoreCursorVisibility ()
+ {
+ Application.Driver.GetCursorVisibility (out _visibility);
+ if (_desiredCursorVisibility != _savedCursorVisibility || _visibility != _savedCursorVisibility) {
+ DesiredCursorVisibility = _savedCursorVisibility;
+ }
+ }
+
+ ///
+ public override void OnDrawContent (Rect contentArea)
+ {
+ _isDrawing = true;
+
+ var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground);
+ SetSelectedStartSelectedLength ();
+
+ Driver.SetAttribute (GetNormalColor ());
+ Move (0, 0);
+
+ var p = ScrollOffset;
+ var col = 0;
+ var width = Frame.Width + OffSetBackground ();
+ var tcount = _text.Count;
+ var roc = GetReadOnlyColor ();
+ for (var idx = p; idx < tcount; idx++) {
+ var rune = _text [idx];
+ var cols = rune.GetColumns ();
+ if (idx == _cursorPosition && HasFocus && !Used && SelectedLength == 0 && !ReadOnly) {
+ Driver.SetAttribute (selColor);
+ } else if (ReadOnly) {
+ Driver.SetAttribute (idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength ? selColor : roc);
+ } else if (!HasFocus && Enabled) {
+ Driver.SetAttribute (ColorScheme.Focus);
+ } else if (!Enabled) {
+ Driver.SetAttribute (roc);
+ } else {
+ Driver.SetAttribute (idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength ? selColor : ColorScheme.Focus);
+ }
+ if (col + cols <= width) {
+ Driver.AddRune (Secret ? Glyphs.Dot : rune);
+ }
+ if (!TextModel.SetCol (ref col, width, cols)) {
+ break;
+ }
+ if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width) {
+ break;
}
}
- CursorVisibility _savedCursorVisibility;
-
- void HideCursorVisibility ()
- {
- if (_desiredCursorVisibility != CursorVisibility.Invisible) {
- DesiredCursorVisibility = CursorVisibility.Invisible;
- }
+ Driver.SetAttribute (ColorScheme.Focus);
+ for (var i = col; i < width; i++) {
+ Driver.AddRune ((Rune)' ');
}
- CursorVisibility _visibility;
+ PositionCursor ();
- void RestoreCursorVisibility ()
- {
- Application.Driver.GetCursorVisibility (out _visibility);
- if (_desiredCursorVisibility != _savedCursorVisibility || _visibility != _savedCursorVisibility) {
- DesiredCursorVisibility = _savedCursorVisibility;
- }
+ RenderCaption ();
+
+ ProcessAutocomplete ();
+
+ _isDrawing = false;
+ }
+
+ void ProcessAutocomplete ()
+ {
+ if (_isDrawing) {
+ return;
+ }
+ if (SelectedLength > 0) {
+ return;
}
- bool _isDrawing = false;
+ // draw autocomplete
+ GenerateSuggestions ();
- ///
- public override void OnDrawContent (Rect contentArea)
- {
- _isDrawing = true;
+ var renderAt = new Point (
+ Autocomplete.Context.CursorPosition, 0);
- var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground);
- SetSelectedStartSelectedLength ();
+ Autocomplete.RenderOverlay (renderAt);
+ }
- Driver.SetAttribute (GetNormalColor ());
- Move (0, 0);
+ void RenderCaption ()
+ {
+ if (HasFocus || Caption == null || Caption.Length == 0
+ || Text?.Length > 0) {
+ return;
+ }
- int p = _first;
- int col = 0;
- int width = Frame.Width + OffSetBackground ();
- var tcount = _text.Count;
- var roc = GetReadOnlyColor ();
- for (int idx = p; idx < tcount; idx++) {
- var rune = _text [idx];
- var cols = rune.GetColumns ();
- if (idx == _cursorPosition && HasFocus && !Used && _length == 0 && !ReadOnly) {
- Driver.SetAttribute (selColor);
- } else if (ReadOnly) {
- Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : roc);
- } else if (!HasFocus && Enabled) {
- Driver.SetAttribute (ColorScheme.Focus);
- } else if (!Enabled) {
- Driver.SetAttribute (roc);
- } else {
- Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : ColorScheme.Focus);
- }
- if (col + cols <= width) {
- Driver.AddRune ((Secret ? CM.Glyphs.Dot : rune));
- }
- if (!TextModel.SetCol (ref col, width, cols)) {
- break;
- }
- if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width) {
- break;
- }
- }
+ var color = new Attribute (CaptionColor, GetNormalColor ().Background);
+ Driver.SetAttribute (color);
- Driver.SetAttribute (ColorScheme.Focus);
- for (int i = col; i < width; i++) {
- Driver.AddRune ((Rune)' ');
- }
+ Move (0, 0);
+ var render = Caption;
+ if (render.GetColumns () > Bounds.Width) {
+ render = render [..Bounds.Width];
+ }
+
+ Driver.AddStr (render);
+ }
+
+ void GenerateSuggestions ()
+ {
+ var currentLine = TextModel.ToRuneCellList (Text);
+ var cursorPosition = Math.Min (CursorPosition, currentLine.Count);
+ Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition,
+ Autocomplete.Context != null ? Autocomplete.Context.Canceled : false);
+
+ Autocomplete.GenerateSuggestions (
+ Autocomplete.Context);
+ }
+
+ ///
+ public override Attribute GetNormalColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+
+ Attribute GetReadOnlyColor ()
+ {
+ if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) {
+ return new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background);
+ }
+ return new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background);
+ }
+
+ void Adjust ()
+ {
+ if (!IsAdded) {
+ return;
+ }
+
+ var offB = OffSetBackground ();
+ var need = NeedsDisplay || !Used;
+ if (_cursorPosition < ScrollOffset) {
+ ScrollOffset = _cursorPosition;
+ need = true;
+ } else if (Frame.Width > 0 && (ScrollOffset + _cursorPosition - (Frame.Width + offB) == 0 ||
+ TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Frame.Width + offB)) {
+
+ ScrollOffset = Math.Max (TextModel.CalculateLeftColumn (_text, ScrollOffset,
+ _cursorPosition, Frame.Width + offB), 0);
+ need = true;
+ }
+ if (need) {
+ SetNeedsDisplay ();
+ } else {
PositionCursor ();
+ }
+ }
- RenderCaption ();
-
- ProcessAutocomplete ();
-
- _isDrawing = false;
+ int OffSetBackground ()
+ {
+ var offB = 0;
+ if (SuperView?.Frame.Right - Frame.Right < 0) {
+ offB = SuperView.Frame.Right - Frame.Right - 1;
}
- private void ProcessAutocomplete ()
- {
- if (_isDrawing) {
- return;
- }
- if (SelectedLength > 0) {
- return;
- }
+ return offB;
+ }
- // draw autocomplete
- GenerateSuggestions ();
+ void SetText (List newText) => Text = StringExtensions.ToString (newText);
- var renderAt = new Point (
- Autocomplete.Context.CursorPosition, 0);
+ void SetText (IEnumerable newText) => SetText (newText.ToList ());
- Autocomplete.RenderOverlay (renderAt);
+ void SetClipboard (IEnumerable text)
+ {
+ if (!Secret) {
+ Clipboard.Contents = StringExtensions.ToString (text.ToList ());
+ }
+ }
+
+ ///
+ public override bool? OnInvokingKeyBindings (Key a)
+ {
+ // Give autocomplete first opportunity to respond to key presses
+ if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) {
+ return true;
+ }
+ return base.OnInvokingKeyBindings (a);
+ }
+
+ /// TODO: Flush out these docs
+ ///
+ /// Processes key presses for the .
+ ///
+ /// The control responds to the following keys:
+ ///
+ ///
+ /// Keys
+ /// Function
+ ///
+ /// -
+ /// ,
+ /// Deletes the character before cursor.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override bool OnProcessKeyDown (Key a)
+ {
+ // Remember the cursor position because the new calculated cursor position is needed
+ // to be set BEFORE the TextChanged event is triggered.
+ // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
+ _preTextChangedCursorPos = _cursorPosition;
+
+ // Ignore other control characters.
+ if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) {
+ return false;
}
- private void RenderCaption ()
- {
- if (HasFocus || Caption == null || Caption.Length == 0
- || Text?.Length > 0) {
- return;
- }
-
- var color = new Attribute (CaptionColor, GetNormalColor ().Background);
- Driver.SetAttribute (color);
-
- Move (0, 0);
- var render = Caption;
-
- if (render.GetColumns () > Bounds.Width) {
- render = render [..Bounds.Width];
- }
-
- Driver.AddStr (render);
- }
-
- private void GenerateSuggestions ()
- {
- var currentLine = TextModel.ToRuneCellList (Text);
- var cursorPosition = Math.Min (this.CursorPosition, currentLine.Count);
- Autocomplete.Context = new AutocompleteContext (currentLine, cursorPosition,
- Autocomplete.Context != null ? Autocomplete.Context.Canceled : false);
-
- Autocomplete.GenerateSuggestions (
- Autocomplete.Context);
- }
-
- ///
- public override Attribute GetNormalColor ()
- {
- return Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
- }
-
- Attribute GetReadOnlyColor ()
- {
- if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) {
- return new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background);
- }
- return new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background);
- }
-
- void Adjust ()
- {
- if (!IsAdded) {
- return;
- }
-
- int offB = OffSetBackground ();
- bool need = NeedsDisplay || !Used;
- if (_cursorPosition < _first) {
- _first = _cursorPosition;
- need = true;
- } else if (Frame.Width > 0 && (_first + _cursorPosition - (Frame.Width + offB) == 0 ||
- TextModel.DisplaySize (_text, _first, _cursorPosition).size >= Frame.Width + offB)) {
-
- _first = Math.Max (TextModel.CalculateLeftColumn (_text, _first,
- _cursorPosition, Frame.Width + offB), 0);
- need = true;
- }
- if (need) {
- SetNeedsDisplay ();
- } else {
- PositionCursor ();
- }
- }
-
- int OffSetBackground ()
- {
- int offB = 0;
- if (SuperView?.Frame.Right - Frame.Right < 0) {
- offB = SuperView.Frame.Right - Frame.Right - 1;
- }
-
- return offB;
- }
-
- void SetText (List newText)
- {
- Text = StringExtensions.ToString (newText);
- }
-
- void SetText (IEnumerable newText)
- {
- SetText (newText.ToList ());
- }
-
- ///
- public override bool CanFocus {
- get => base.CanFocus;
- set { base.CanFocus = value; }
- }
-
- void SetClipboard (IEnumerable text)
- {
- if (!Secret)
- Clipboard.Contents = StringExtensions.ToString (text.ToList ());
- }
-
- int _preTextChangedCursorPos;
-
- ///
- public override bool? OnInvokingKeyBindings (Key a)
- {
- // Give autocomplete first opportunity to respond to key presses
- if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (a)) {
- return true;
- }
- return base.OnInvokingKeyBindings (a);
- }
-
- /// TODO: Flush out these docs
- ///
- /// Processes key presses for the .
- ///
- /// The control responds to the following keys:
- ///
- ///
- /// Keys
- /// Function
- ///
- /// -
- /// ,
- /// Deletes the character before cursor.
- ///
- ///
- ///
- ///
- ///
- ///
- public override bool OnProcessKeyDown (Key a)
- {
- // Remember the cursor position because the new calculated cursor position is needed
- // to be set BEFORE the TextChanged event is triggered.
- // Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
- _preTextChangedCursorPos = _cursorPosition;
-
- // Ignore other control characters.
- if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask)) {
- return false;
- }
-
- if (ReadOnly) {
- return true;
- }
-
- InsertText (a, true);
-
+ if (ReadOnly) {
return true;
}
- void InsertText (Key a, bool usePreTextChangedCursorPos)
- {
- _historyText.Add (new List> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
+ InsertText (a, true);
- List newText = _text;
- if (_length > 0) {
- newText = DeleteSelectedText ();
- _preTextChangedCursorPos = _cursorPosition;
- }
- if (!usePreTextChangedCursorPos) {
- _preTextChangedCursorPos = _cursorPosition;
- }
- var kbstr = a.AsRune.ToString ().EnumerateRunes ();
- if (Used) {
- _cursorPosition++;
- if (_cursorPosition == newText.Count + 1) {
- SetText (newText.Concat (kbstr).ToList ());
- } else {
- if (_preTextChangedCursorPos > newText.Count) {
- _preTextChangedCursorPos = newText.Count;
- }
- SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (_preTextChangedCursorPos, Math.Min (newText.Count - _preTextChangedCursorPos, newText.Count))));
- }
- } else {
- SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (Math.Min (_preTextChangedCursorPos + 1, newText.Count), Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0))));
- _cursorPosition++;
- }
- Adjust ();
+ return true;
+ }
+
+ void InsertText (Key a, bool usePreTextChangedCursorPos)
+ {
+ _historyText.Add (new List> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
+
+ var newText = _text;
+ if (SelectedLength > 0) {
+ newText = DeleteSelectedText ();
+ _preTextChangedCursorPos = _cursorPosition;
}
-
- void SetOverwrite (bool overwrite)
- {
- Used = overwrite;
- SetNeedsDisplay ();
+ if (!usePreTextChangedCursorPos) {
+ _preTextChangedCursorPos = _cursorPosition;
}
-
- TextModel GetModel ()
- {
- var model = new TextModel ();
- model.LoadString (Text);
- return model;
- }
-
- ///
- /// Deletes word backwards.
- ///
- public virtual void KillWordBackwards ()
- {
- ClearAllSelection ();
- var newPos = GetModel ().WordBackward (_cursorPosition, 0);
- if (newPos == null) return;
- if (newPos.Value.col != -1) {
- SetText (_text.GetRange (0, newPos.Value.col).Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)));
- _cursorPosition = newPos.Value.col;
- }
- Adjust ();
- }
-
- ///
- /// Deletes word forwards.
- ///
- public virtual void KillWordForwards ()
- {
- ClearAllSelection ();
- var newPos = GetModel ().WordForward (_cursorPosition, 0);
- if (newPos == null) return;
- if (newPos.Value.col != -1) {
- SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col)));
- }
- Adjust ();
- }
-
- void MoveWordRight ()
- {
- ClearAllSelection ();
- var newPos = GetModel ().WordForward (_cursorPosition, 0);
- if (newPos == null) return;
- if (newPos.Value.col != -1)
- _cursorPosition = newPos.Value.col;
- Adjust ();
- }
-
- void MoveWordLeft ()
- {
- ClearAllSelection ();
- var newPos = GetModel ().WordBackward (_cursorPosition, 0);
- if (newPos == null) return;
- if (newPos.Value.col != -1)
- _cursorPosition = newPos.Value.col;
- Adjust ();
- }
-
- ///
- /// Redoes the latest changes.
- ///
- public void Redo ()
- {
- if (ReadOnly) {
- return;
- }
-
- _historyText.Redo ();
-
- //if (string.IsNullOrEmpty (Clipboard.Contents))
- // return true;
- //var clip = TextModel.ToRunes (Clipboard.Contents);
- //if (clip == null)
- // return true;
-
- //if (point == text.Count) {
- // point = text.Count;
- // SetText(text.Concat(clip).ToList());
- //} else {
- // point += clip.Count;
- // SetText(text.GetRange(0, oldCursorPos).Concat(clip).Concat(text.GetRange(oldCursorPos, text.Count - oldCursorPos)));
- //}
- //Adjust ();
- }
-
- ///
- /// Undoes the latest changes.
- ///
- public void Undo ()
- {
- if (ReadOnly) {
- return;
- }
-
- _historyText.Undo ();
- }
-
- void KillToStart ()
- {
- if (ReadOnly)
- return;
-
- ClearAllSelection ();
- if (_cursorPosition == 0)
- return;
- SetClipboard (_text.GetRange (0, _cursorPosition));
- SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
- _cursorPosition = 0;
- Adjust ();
- }
-
- void KillToEnd ()
- {
- if (ReadOnly)
- return;
-
- ClearAllSelection ();
- if (_cursorPosition >= _text.Count)
- return;
- SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
- SetText (_text.GetRange (0, _cursorPosition));
- Adjust ();
- }
-
- void MoveRight ()
- {
- ClearAllSelection ();
- if (_cursorPosition == _text.Count)
- return;
+ var kbstr = a.AsRune.ToString ().EnumerateRunes ();
+ if (Used) {
_cursorPosition++;
- Adjust ();
- }
-
- ///
- /// Moves cursor to the end of the typed text.
- ///
- public void MoveEnd ()
- {
- ClearAllSelection ();
- _cursorPosition = _text.Count;
- Adjust ();
- }
-
- void MoveLeft ()
- {
- ClearAllSelection ();
- if (_cursorPosition > 0) {
- _cursorPosition--;
- Adjust ();
+ if (_cursorPosition == newText.Count + 1) {
+ SetText (newText.Concat (kbstr).ToList ());
+ } else {
+ if (_preTextChangedCursorPos > newText.Count) {
+ _preTextChangedCursorPos = newText.Count;
+ }
+ SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (_preTextChangedCursorPos, Math.Min (newText.Count - _preTextChangedCursorPos, newText.Count))));
}
+ } else {
+ SetText (newText.GetRange (0, _preTextChangedCursorPos).Concat (kbstr).Concat (newText.GetRange (Math.Min (_preTextChangedCursorPos + 1, newText.Count), Math.Max (newText.Count - _preTextChangedCursorPos - 1, 0))));
+ _cursorPosition++;
+ }
+ Adjust ();
+ }
+
+ void SetOverwrite (bool overwrite)
+ {
+ Used = overwrite;
+ SetNeedsDisplay ();
+ }
+
+ TextModel GetModel ()
+ {
+ var model = new TextModel ();
+ model.LoadString (Text);
+ return model;
+ }
+
+ ///
+ /// Deletes word backwards.
+ ///
+ public virtual void KillWordBackwards ()
+ {
+ ClearAllSelection ();
+ var newPos = GetModel ().WordBackward (_cursorPosition, 0);
+ if (newPos == null) {
+ return;
+ }
+ if (newPos.Value.col != -1) {
+ SetText (_text.GetRange (0, newPos.Value.col).Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition)));
+ _cursorPosition = newPos.Value.col;
+ }
+ Adjust ();
+ }
+
+ ///
+ /// Deletes word forwards.
+ ///
+ public virtual void KillWordForwards ()
+ {
+ ClearAllSelection ();
+ var newPos = GetModel ().WordForward (_cursorPosition, 0);
+ if (newPos == null) {
+ return;
+ }
+ if (newPos.Value.col != -1) {
+ SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col)));
+ }
+ Adjust ();
+ }
+
+ void MoveWordRight ()
+ {
+ ClearAllSelection ();
+ var newPos = GetModel ().WordForward (_cursorPosition, 0);
+ if (newPos == null) {
+ return;
+ }
+ if (newPos.Value.col != -1) {
+ _cursorPosition = newPos.Value.col;
+ }
+ Adjust ();
+ }
+
+ void MoveWordLeft ()
+ {
+ ClearAllSelection ();
+ var newPos = GetModel ().WordBackward (_cursorPosition, 0);
+ if (newPos == null) {
+ return;
+ }
+ if (newPos.Value.col != -1) {
+ _cursorPosition = newPos.Value.col;
+ }
+ Adjust ();
+ }
+
+ ///
+ /// Redoes the latest changes.
+ ///
+ public void Redo ()
+ {
+ if (ReadOnly) {
+ return;
}
- void MoveWordRightExtend ()
- {
- if (_cursorPosition < _text.Count) {
- int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
- var newPos = GetModel ().WordForward (x, 0);
- if (newPos == null) return;
- if (newPos.Value.col != -1)
+ _historyText.Redo ();
+
+ //if (string.IsNullOrEmpty (Clipboard.Contents))
+ // return true;
+ //var clip = TextModel.ToRunes (Clipboard.Contents);
+ //if (clip == null)
+ // return true;
+
+ //if (point == text.Count) {
+ // point = text.Count;
+ // SetText(text.Concat(clip).ToList());
+ //} else {
+ // point += clip.Count;
+ // SetText(text.GetRange(0, oldCursorPos).Concat(clip).Concat(text.GetRange(oldCursorPos, text.Count - oldCursorPos)));
+ //}
+ //Adjust ();
+ }
+
+ ///
+ /// Undoes the latest changes.
+ ///
+ public void Undo ()
+ {
+ if (ReadOnly) {
+ return;
+ }
+
+ _historyText.Undo ();
+ }
+
+ void KillToStart ()
+ {
+ if (ReadOnly) {
+ return;
+ }
+
+ ClearAllSelection ();
+ if (_cursorPosition == 0) {
+ return;
+ }
+ SetClipboard (_text.GetRange (0, _cursorPosition));
+ SetText (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
+ _cursorPosition = 0;
+ Adjust ();
+ }
+
+ void KillToEnd ()
+ {
+ if (ReadOnly) {
+ return;
+ }
+
+ ClearAllSelection ();
+ if (_cursorPosition >= _text.Count) {
+ return;
+ }
+ SetClipboard (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition));
+ SetText (_text.GetRange (0, _cursorPosition));
+ Adjust ();
+ }
+
+ void MoveRight ()
+ {
+ ClearAllSelection ();
+ if (_cursorPosition == _text.Count) {
+ return;
+ }
+ _cursorPosition++;
+ Adjust ();
+ }
+
+ ///
+ /// Moves cursor to the end of the typed text.
+ ///
+ public void MoveEnd ()
+ {
+ ClearAllSelection ();
+ _cursorPosition = _text.Count;
+ Adjust ();
+ }
+
+ void MoveLeft ()
+ {
+ ClearAllSelection ();
+ if (_cursorPosition > 0) {
+ _cursorPosition--;
+ Adjust ();
+ }
+ }
+
+ void MoveWordRightExtend ()
+ {
+ if (_cursorPosition < _text.Count) {
+ var x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
+ var newPos = GetModel ().WordForward (x, 0);
+ if (newPos == null) {
+ return;
+ }
+ if (newPos.Value.col != -1) {
+ _cursorPosition = newPos.Value.col;
+ }
+ PrepareSelection (x, newPos.Value.col - x);
+ }
+ }
+
+ void MoveWordLeftExtend ()
+ {
+ if (_cursorPosition > 0) {
+ var x = Math.Min (_start > -1 && _start > _cursorPosition ? _start : _cursorPosition, _text.Count);
+ if (x > 0) {
+ var newPos = GetModel ().WordBackward (x, 0);
+ if (newPos == null) {
+ return;
+ }
+ if (newPos.Value.col != -1) {
_cursorPosition = newPos.Value.col;
+ }
PrepareSelection (x, newPos.Value.col - x);
}
}
+ }
- void MoveWordLeftExtend ()
- {
- if (_cursorPosition > 0) {
- int x = Math.Min (_start > -1 && _start > _cursorPosition ? _start : _cursorPosition, _text.Count);
- if (x > 0) {
- var newPos = GetModel ().WordBackward (x, 0);
- if (newPos == null) return;
- if (newPos.Value.col != -1)
- _cursorPosition = newPos.Value.col;
- PrepareSelection (x, newPos.Value.col - x);
- }
- }
+ void MoveRightExtend ()
+ {
+ if (_cursorPosition < _text.Count) {
+ PrepareSelection (_cursorPosition++, 1);
}
+ }
- void MoveRightExtend ()
- {
- if (_cursorPosition < _text.Count) {
- PrepareSelection (_cursorPosition++, 1);
- }
+ void MoveLeftExtend ()
+ {
+ if (_cursorPosition > 0) {
+ PrepareSelection (_cursorPosition--, -1);
}
+ }
- void MoveLeftExtend ()
- {
- if (_cursorPosition > 0) {
- PrepareSelection (_cursorPosition--, -1);
- }
+ void MoveHome ()
+ {
+ ClearAllSelection ();
+ _cursorPosition = 0;
+ Adjust ();
+ }
+
+ void MoveEndExtend ()
+ {
+ if (_cursorPosition <= _text.Count) {
+ var x = _cursorPosition;
+ _cursorPosition = _text.Count;
+ PrepareSelection (x, _cursorPosition - x);
}
+ }
- void MoveHome ()
- {
- ClearAllSelection ();
+ void MoveHomeExtend ()
+ {
+ if (_cursorPosition > 0) {
+ var x = _cursorPosition;
_cursorPosition = 0;
- Adjust ();
+ PrepareSelection (x, _cursorPosition - x);
+ }
+ }
+
+ ///
+ /// Deletes the character to the left.
+ ///
+ ///
+ /// If set to true use the cursor position cached
+ /// ; otherwise use .
+ /// use .
+ ///
+ public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
+ {
+ if (ReadOnly) {
+ return;
}
- void MoveEndExtend ()
- {
- if (_cursorPosition <= _text.Count) {
- int x = _cursorPosition;
- _cursorPosition = _text.Count;
- PrepareSelection (x, _cursorPosition - x);
- }
- }
+ _historyText.Add (new List> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
- void MoveHomeExtend ()
- {
- if (_cursorPosition > 0) {
- int x = _cursorPosition;
- _cursorPosition = 0;
- PrepareSelection (x, _cursorPosition - x);
- }
- }
-
- ///
- /// Deletes the character to the left.
- ///
- /// If set to true use the cursor position cached
- /// ; otherwise use .
- /// use .
- public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
- {
- if (ReadOnly)
- return;
-
- _historyText.Add (new List> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
-
- if (_length == 0) {
- if (_cursorPosition == 0)
- return;
-
- if (!usePreTextChangedCursorPos) {
- _preTextChangedCursorPos = _cursorPosition;
- }
- _cursorPosition--;
- if (_preTextChangedCursorPos < _text.Count) {
- SetText (_text.GetRange (0, _preTextChangedCursorPos - 1).Concat (_text.GetRange (_preTextChangedCursorPos, _text.Count - _preTextChangedCursorPos)));
- } else {
- SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
- }
- Adjust ();
- } else {
- var newText = DeleteSelectedText ();
- Text = StringExtensions.ToString (newText);
- Adjust ();
- }
- }
-
- ///
- /// Deletes the character to the right.
- ///
- public virtual void DeleteCharRight ()
- {
- if (ReadOnly)
- return;
-
- _historyText.Add (new List> () { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
-
- if (_length == 0) {
- if (_text.Count == 0 || _text.Count == _cursorPosition)
- return;
-
- SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1))));
- Adjust ();
- } else {
- var newText = DeleteSelectedText ();
- Text = StringExtensions.ToString (newText);
- Adjust ();
- }
- }
-
- void ShowContextMenu ()
- {
- if (_currentCulture != Thread.CurrentThread.CurrentUICulture) {
-
- _currentCulture = Thread.CurrentThread.CurrentUICulture;
-
- ContextMenu.MenuItems = BuildContextMenuBarItem ();
- }
- ContextMenu.Show ();
- }
-
- ///
- /// Selects all text.
- ///
- public void SelectAll ()
- {
- if (_text.Count == 0) {
+ if (SelectedLength == 0) {
+ if (_cursorPosition == 0) {
return;
}
- _selectedStart = 0;
- MoveEndExtend ();
- SetNeedsDisplay ();
- }
-
- ///
- /// Deletes all text.
- ///
- public void DeleteAll ()
- {
- if (_text.Count == 0) {
- return;
+ if (!usePreTextChangedCursorPos) {
+ _preTextChangedCursorPos = _cursorPosition;
}
-
- _selectedStart = 0;
- MoveEndExtend ();
- DeleteCharLeft (false);
- SetNeedsDisplay ();
- }
-
- ///
- /// Start position of the selected text.
- ///
- public int SelectedStart {
- get => _selectedStart;
- set {
- if (value < -1) {
- _selectedStart = -1;
- } else if (value > _text.Count) {
- _selectedStart = _text.Count;
- } else {
- _selectedStart = value;
- }
- PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
- }
- }
-
- ///
- /// Length of the selected text.
- ///
- public int SelectedLength { get => _length; }
-
- ///
- /// The selected text.
- ///
- public string SelectedText {
- get => Secret ? null : _selectedText;
- private set => _selectedText = value;
- }
-
- int _start, _length;
- bool _isButtonPressed;
- bool _isButtonReleased = true;
-
- ///
- public override bool MouseEvent (MouseEvent ev)
- {
- if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed) && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition) &&
- !ev.Flags.HasFlag (MouseFlags.Button1Released) && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
- !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) && !ev.Flags.HasFlag (ContextMenu.MouseFlags)) {
- return false;
- }
-
- if (!CanFocus) {
- return true;
- }
-
- if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition) {
- SetFocus ();
- }
-
- // Give autocomplete first opportunity to respond to mouse clicks
- if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) {
- return true;
- }
-
- if (ev.Flags == MouseFlags.Button1Pressed) {
- EnsureHasFocus ();
- PositionCursor (ev);
- if (_isButtonReleased) {
- ClearAllSelection ();
- }
- _isButtonReleased = true;
- _isButtonPressed = true;
- } else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed) {
- int x = PositionCursor (ev);
- _isButtonReleased = false;
- PrepareSelection (x);
- if (Application.MouseGrabView == null) {
- Application.GrabMouse (this);
- }
- } else if (ev.Flags == MouseFlags.Button1Released) {
- _isButtonReleased = true;
- _isButtonPressed = false;
- Application.UngrabMouse ();
- } else if (ev.Flags == MouseFlags.Button1DoubleClicked) {
- EnsureHasFocus ();
- int x = PositionCursor (ev);
- int sbw = x;
- if (x == _text.Count || (x > 0 && (char)_text [x - 1].Value != ' ')
- || (x > 0 && (char)_text [x].Value == ' ')) {
-
- var newPosBw = GetModel ().WordBackward (x, 0);
- if (newPosBw == null) return true;
- sbw = newPosBw.Value.col;
- }
- if (sbw != -1) {
- x = sbw;
- PositionCursor (x);
- }
- var newPosFw = GetModel ().WordForward (x, 0);
- if (newPosFw == null) return true;
- ClearAllSelection ();
- if (newPosFw.Value.col != -1 && sbw != -1) {
- _cursorPosition = newPosFw.Value.col;
- }
- PrepareSelection (sbw, newPosFw.Value.col - sbw);
- } else if (ev.Flags == MouseFlags.Button1TripleClicked) {
- EnsureHasFocus ();
- PositionCursor (0);
- ClearAllSelection ();
- PrepareSelection (0, _text.Count);
- } else if (ev.Flags == ContextMenu.MouseFlags) {
- ShowContextMenu ();
- }
-
- SetNeedsDisplay ();
- return true;
-
- void EnsureHasFocus ()
- {
- if (!HasFocus) {
- SetFocus ();
- }
- }
- }
-
- int PositionCursor (MouseEvent ev)
- {
- // We could also set the cursor position.
- int x;
- var pX = TextModel.GetColFromX (_text, _first, ev.X);
- if (_text.Count == 0) {
- x = pX - ev.OfX;
+ _cursorPosition--;
+ if (_preTextChangedCursorPos < _text.Count) {
+ SetText (_text.GetRange (0, _preTextChangedCursorPos - 1).Concat (_text.GetRange (_preTextChangedCursorPos, _text.Count - _preTextChangedCursorPos)));
} else {
- x = pX;
- }
- return PositionCursor (x, false);
- }
-
- int PositionCursor (int x, bool getX = true)
- {
- int pX = x;
- if (getX) {
- pX = TextModel.GetColFromX (_text, _first, x);
- }
- if (_first + pX > _text.Count) {
- _cursorPosition = _text.Count;
- } else if (_first + pX < _first) {
- _cursorPosition = 0;
- } else {
- _cursorPosition = _first + pX;
- }
-
- return _cursorPosition;
- }
-
- void PrepareSelection (int x, int direction = 0)
- {
- x = x + _first < -1 ? 0 : x;
- _selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count ? x : _selectedStart;
- if (_selectedStart > -1) {
- _length = Math.Abs (x + direction <= _text.Count ? x + direction - _selectedStart : _text.Count - _selectedStart);
- SetSelectedStartSelectedLength ();
- if (_start > -1 && _length > 0) {
- _selectedText = _length > 0 ? StringExtensions.ToString (_text.GetRange (
- _start < 0 ? 0 : _start, _length > _text.Count ? _text.Count : _length)) : "";
- if (_first > _start) {
- _first = _start;
- }
- } else if (_start > -1 && _length == 0) {
- _selectedText = null;
- }
- SetNeedsDisplay ();
- } else if (_length > 0 || _selectedText != null) {
- ClearAllSelection ();
+ SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
}
Adjust ();
- }
-
- ///
- /// Clear the selected text.
- ///
- public void ClearAllSelection ()
- {
- if (_selectedStart == -1 && _length == 0 && string.IsNullOrEmpty (_selectedText)) {
- return;
- }
-
- _selectedStart = -1;
- _length = 0;
- _selectedText = null;
- _start = 0;
- _length = 0;
- SetNeedsDisplay ();
- }
-
- void SetSelectedStartSelectedLength ()
- {
- if (SelectedStart > -1 && _cursorPosition < SelectedStart) {
- _start = _cursorPosition;
- } else {
- _start = SelectedStart;
- }
- }
-
- ///
- /// Copy the selected text to the clipboard.
- ///
- public virtual void Copy ()
- {
- if (Secret || _length == 0)
- return;
-
- Clipboard.Contents = SelectedText;
- }
-
- ///
- /// Cut the selected text to the clipboard.
- ///
- public virtual void Cut ()
- {
- if (ReadOnly || Secret || _length == 0)
- return;
-
- Clipboard.Contents = SelectedText;
+ } else {
var newText = DeleteSelectedText ();
Text = StringExtensions.ToString (newText);
Adjust ();
}
+ }
- List DeleteSelectedText ()
- {
- SetSelectedStartSelectedLength ();
- int selStart = SelectedStart > -1 ? _start : _cursorPosition;
- var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) +
- StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
-
- ClearAllSelection ();
- _cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
- return newText.ToRuneList ();
+ ///
+ /// Deletes the character to the right.
+ ///
+ public virtual void DeleteCharRight ()
+ {
+ if (ReadOnly) {
+ return;
}
- ///
- /// Paste the selected text from the clipboard.
- ///
- public virtual void Paste ()
- {
- if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents)) {
+ _historyText.Add (new List> { TextModel.ToRuneCells (_text) }, new Point (_cursorPosition, 0));
+
+ if (SelectedLength == 0) {
+ if (_text.Count == 0 || _text.Count == _cursorPosition) {
return;
}
- SetSelectedStartSelectedLength ();
- int selStart = _start == -1 ? CursorPosition : _start;
- string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
- Text = StringExtensions.ToString (_text.GetRange (0, selStart)) +
- cbTxt +
- StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
+ SetText (_text.GetRange (0, _cursorPosition).Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1))));
+ Adjust ();
+ } else {
+ var newText = DeleteSelectedText ();
+ Text = StringExtensions.ToString (newText);
+ Adjust ();
+ }
+ }
- _cursorPosition = selStart + cbTxt.GetRuneCount ();
+ void ShowContextMenu ()
+ {
+ if (_currentCulture != Thread.CurrentThread.CurrentUICulture) {
+
+ _currentCulture = Thread.CurrentThread.CurrentUICulture;
+
+ ContextMenu.MenuItems = BuildContextMenuBarItem ();
+ }
+ ContextMenu.Show ();
+ }
+
+ ///
+ /// Selects all text.
+ ///
+ public void SelectAll ()
+ {
+ if (_text.Count == 0) {
+ return;
+ }
+
+ _selectedStart = 0;
+ MoveEndExtend ();
+ SetNeedsDisplay ();
+ }
+
+ ///
+ /// Deletes all text.
+ ///
+ public void DeleteAll ()
+ {
+ if (_text.Count == 0) {
+ return;
+ }
+
+ _selectedStart = 0;
+ MoveEndExtend ();
+ DeleteCharLeft (false);
+ SetNeedsDisplay ();
+ }
+
+ ///
+ public override bool MouseEvent (MouseEvent ev)
+ {
+ if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed) && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition) &&
+ !ev.Flags.HasFlag (MouseFlags.Button1Released) && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
+ !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked) && !ev.Flags.HasFlag (ContextMenu.MouseFlags)) {
+ return false;
+ }
+
+ if (!CanFocus) {
+ return true;
+ }
+
+ if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition) {
+ SetFocus ();
+ }
+
+ // Give autocomplete first opportunity to respond to mouse clicks
+ if (SelectedLength == 0 && Autocomplete.MouseEvent (ev, true)) {
+ return true;
+ }
+
+ if (ev.Flags == MouseFlags.Button1Pressed) {
+ EnsureHasFocus ();
+ PositionCursor (ev);
+ if (_isButtonReleased) {
+ ClearAllSelection ();
+ }
+ _isButtonReleased = true;
+ _isButtonPressed = true;
+ } else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed) {
+ var x = PositionCursor (ev);
+ _isButtonReleased = false;
+ PrepareSelection (x);
+ if (Application.MouseGrabView == null) {
+ Application.GrabMouse (this);
+ }
+ } else if (ev.Flags == MouseFlags.Button1Released) {
+ _isButtonReleased = true;
+ _isButtonPressed = false;
+ Application.UngrabMouse ();
+ } else if (ev.Flags == MouseFlags.Button1DoubleClicked) {
+ EnsureHasFocus ();
+ var x = PositionCursor (ev);
+ var sbw = x;
+ if (x == _text.Count || x > 0 && (char)_text [x - 1].Value != ' '
+ || x > 0 && (char)_text [x].Value == ' ') {
+
+ var newPosBw = GetModel ().WordBackward (x, 0);
+ if (newPosBw == null) {
+ return true;
+ }
+ sbw = newPosBw.Value.col;
+ }
+ if (sbw != -1) {
+ x = sbw;
+ PositionCursor (x);
+ }
+ var newPosFw = GetModel ().WordForward (x, 0);
+ if (newPosFw == null) {
+ return true;
+ }
+ ClearAllSelection ();
+ if (newPosFw.Value.col != -1 && sbw != -1) {
+ _cursorPosition = newPosFw.Value.col;
+ }
+ PrepareSelection (sbw, newPosFw.Value.col - sbw);
+ } else if (ev.Flags == MouseFlags.Button1TripleClicked) {
+ EnsureHasFocus ();
+ PositionCursor (0);
+ ClearAllSelection ();
+ PrepareSelection (0, _text.Count);
+ } else if (ev.Flags == ContextMenu.MouseFlags) {
+ ShowContextMenu ();
+ }
+
+ SetNeedsDisplay ();
+ return true;
+
+ void EnsureHasFocus ()
+ {
+ if (!HasFocus) {
+ SetFocus ();
+ }
+ }
+ }
+
+ int PositionCursor (MouseEvent ev)
+ {
+ // We could also set the cursor position.
+ int x;
+ var pX = TextModel.GetColFromX (_text, ScrollOffset, ev.X);
+ if (_text.Count == 0) {
+ x = pX - ev.OfX;
+ } else {
+ x = pX;
+ }
+ return PositionCursor (x, false);
+ }
+
+ int PositionCursor (int x, bool getX = true)
+ {
+ var pX = x;
+ if (getX) {
+ pX = TextModel.GetColFromX (_text, ScrollOffset, x);
+ }
+ if (ScrollOffset + pX > _text.Count) {
+ _cursorPosition = _text.Count;
+ } else if (ScrollOffset + pX < ScrollOffset) {
+ _cursorPosition = 0;
+ } else {
+ _cursorPosition = ScrollOffset + pX;
+ }
+
+ return _cursorPosition;
+ }
+
+ void PrepareSelection (int x, int direction = 0)
+ {
+ x = x + ScrollOffset < -1 ? 0 : x;
+ _selectedStart = _selectedStart == -1 && _text.Count > 0 && x >= 0 && x <= _text.Count ? x : _selectedStart;
+ if (_selectedStart > -1) {
+ SelectedLength = Math.Abs (x + direction <= _text.Count ? x + direction - _selectedStart : _text.Count - _selectedStart);
+ SetSelectedStartSelectedLength ();
+ if (_start > -1 && SelectedLength > 0) {
+ _selectedText = SelectedLength > 0 ? StringExtensions.ToString (_text.GetRange (
+ _start < 0 ? 0 : _start, SelectedLength > _text.Count ? _text.Count : SelectedLength)) : "";
+ if (ScrollOffset > _start) {
+ ScrollOffset = _start;
+ }
+ } else if (_start > -1 && SelectedLength == 0) {
+ _selectedText = null;
+ }
+ SetNeedsDisplay ();
+ } else if (SelectedLength > 0 || _selectedText != null) {
+ ClearAllSelection ();
+ }
+ Adjust ();
+ }
+
+ ///
+ /// Clear the selected text.
+ ///
+ public void ClearAllSelection ()
+ {
+ if (_selectedStart == -1 && SelectedLength == 0 && string.IsNullOrEmpty (_selectedText)) {
+ return;
+ }
+
+ _selectedStart = -1;
+ SelectedLength = 0;
+ _selectedText = null;
+ _start = 0;
+ SelectedLength = 0;
+ SetNeedsDisplay ();
+ }
+
+ void SetSelectedStartSelectedLength ()
+ {
+ if (SelectedStart > -1 && _cursorPosition < SelectedStart) {
+ _start = _cursorPosition;
+ } else {
+ _start = SelectedStart;
+ }
+ }
+
+ ///
+ /// Copy the selected text to the clipboard.
+ ///
+ public virtual void Copy ()
+ {
+ if (Secret || SelectedLength == 0) {
+ return;
+ }
+
+ Clipboard.Contents = SelectedText;
+ }
+
+ ///
+ /// Cut the selected text to the clipboard.
+ ///
+ public virtual void Cut ()
+ {
+ if (ReadOnly || Secret || SelectedLength == 0) {
+ return;
+ }
+
+ Clipboard.Contents = SelectedText;
+ var newText = DeleteSelectedText ();
+ Text = StringExtensions.ToString (newText);
+ Adjust ();
+ }
+
+ List DeleteSelectedText ()
+ {
+ SetSelectedStartSelectedLength ();
+ var selStart = SelectedStart > -1 ? _start : _cursorPosition;
+ var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) +
+ StringExtensions.ToString (_text.GetRange (selStart + SelectedLength, _text.Count - (selStart + SelectedLength)));
+
+ ClearAllSelection ();
+ _cursorPosition = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
+ return newText.ToRuneList ();
+ }
+
+ ///
+ /// Paste the selected text from the clipboard.
+ ///
+ public virtual void Paste ()
+ {
+ if (ReadOnly || string.IsNullOrEmpty (Clipboard.Contents)) {
+ return;
+ }
+
+ SetSelectedStartSelectedLength ();
+ var selStart = _start == -1 ? CursorPosition : _start;
+ var cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
+ Text = StringExtensions.ToString (_text.GetRange (0, selStart)) +
+ cbTxt +
+ StringExtensions.ToString (_text.GetRange (selStart + SelectedLength, _text.Count - (selStart + SelectedLength)));
+
+ _cursorPosition = Math.Min (selStart + cbTxt.GetRuneCount (), _text.Count);
ClearAllSelection ();
SetNeedsDisplay ();
Adjust ();
}
- ///
- /// Virtual method that invoke the event if it's defined.
- ///
- /// The new text to be replaced.
- /// Returns the
- public virtual TextChangingEventArgs OnTextChanging (string newText)
- {
- var ev = new TextChangingEventArgs (newText);
- TextChanging?.Invoke (this, ev);
- return ev;
- }
-
- CursorVisibility _desiredCursorVisibility = CursorVisibility.Default;
-
- ///
- /// Get / Set the wished cursor when the field is focused
- ///
- public CursorVisibility DesiredCursorVisibility {
- get => _desiredCursorVisibility;
- set {
- if ((_desiredCursorVisibility != value || _visibility != value) && HasFocus) {
- Application.Driver.SetCursorVisibility (value);
- }
-
- _desiredCursorVisibility = _visibility = value;
- }
- }
-
- ///
- /// Inserts the given text at the current cursor position
- /// exactly as if the user had just typed it
- ///
- /// Text to add
- /// Use the previous cursor position.
- public void InsertText (string toAdd, bool useOldCursorPos = true)
- {
- foreach (var ch in toAdd) {
-
- KeyCode key;
-
- try {
- key = (KeyCode)ch;
- } catch (Exception) {
-
- throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
- }
-
- InsertText (new Key () { KeyCode = key }, useOldCursorPos);
- }
- }
-
- ///
- /// Allows clearing the items updating the original text.
- ///
- public void ClearHistoryChanges ()
- {
- _historyText.Clear (Text);
- }
-
- ///
- /// Returns if the current cursor position is
- /// at the end of the . This includes when it is empty.
- ///
- ///
- internal bool CursorIsAtEnd ()
- {
- return CursorPosition == Text.Length;
- }
-
- ///
- /// Returns if the current cursor position is
- /// at the start of the .
- ///
- ///
- internal bool CursorIsAtStart ()
- {
- return CursorPosition <= 0;
- }
- }
///
- /// Renders an overlay on another view at a given point that allows selecting
- /// from a range of 'autocomplete' options.
- /// An implementation on a TextField.
+ /// Virtual method that invoke the event if it's defined.
///
- public class TextFieldAutocomplete : PopupAutocomplete {
+ /// The new text to be replaced.
+ /// Returns the
+ public virtual TextChangingEventArgs OnTextChanging (string newText)
+ {
+ var ev = new TextChangingEventArgs (newText);
+ TextChanging?.Invoke (this, ev);
+ return ev;
+ }
- ///
- protected override void DeleteTextBackwards ()
- {
- ((TextField)HostControl).DeleteCharLeft (false);
- }
+ ///
+ /// Inserts the given text at the current cursor position
+ /// exactly as if the user had just typed it
+ ///
+ /// Text to add
+ /// Use the previous cursor position.
+ public void InsertText (string toAdd, bool useOldCursorPos = true)
+ {
+ foreach (var ch in toAdd) {
- ///
- protected override void InsertText (string accepted)
- {
- ((TextField)HostControl).InsertText (accepted, false);
- }
+ KeyCode key;
- ///
- protected override void SetCursorPosition (int column)
- {
- ((TextField)HostControl).CursorPosition = column;
+ try {
+ key = (KeyCode)ch;
+ } catch (Exception) {
+
+ throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
+ }
+
+ InsertText (new Key { KeyCode = key }, useOldCursorPos);
}
}
+
+ ///
+ /// Allows clearing the items updating the original text.
+ ///
+ public void ClearHistoryChanges () => _historyText.Clear (Text);
+
+ ///
+ /// Returns if the current cursor position is
+ /// at the end of the . This includes when it is empty.
+ ///
+ ///
+ internal bool CursorIsAtEnd () => CursorPosition == Text.Length;
+
+ ///
+ /// Returns if the current cursor position is
+ /// at the start of the .
+ ///
+ ///
+ internal bool CursorIsAtStart () => CursorPosition <= 0;
+}
+
+///
+/// Renders an overlay on another view at a given point that allows selecting
+/// from a range of 'autocomplete' options.
+/// An implementation on a TextField.
+///
+public class TextFieldAutocomplete : PopupAutocomplete {
+
+ ///
+ protected override void DeleteTextBackwards () => ((TextField)HostControl).DeleteCharLeft (false);
+
+ ///
+ protected override void InsertText (string accepted) => ((TextField)HostControl).InsertText (accepted, false);
+
+ ///
+ protected override void SetCursorPosition (int column) => ((TextField)HostControl).CursorPosition = column;
}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
index 41357cb9f..1b4294475 100644
--- a/Terminal.Gui/Views/TextView.cs
+++ b/Terminal.Gui/Views/TextView.cs
@@ -546,8 +546,8 @@ class TextModel {
string ReplaceText (List source, string textToReplace, string matchText, int col)
{
var origTxt = ToString (source);
- (var _, var len) = DisplaySize (source, 0, col, false);
- (var _, var len2) = DisplaySize (source, col, col + matchText.Length, false);
+ (var _, var len) = DisplaySize (source, 0, col, false);
+ (var _, var len2) = DisplaySize (source, col, col + matchText.Length, false);
(var _, var len3) = DisplaySize (source, col + matchText.Length, origTxt.GetRuneCount (), false);
return origTxt [..len] +
@@ -2158,9 +2158,9 @@ public class TextView : View {
SetOverwrite (false);
return true;
});
- AddCommand (Command.Tab, () => ProcessTab ());
- AddCommand (Command.BackTab, () => ProcessBackTab ());
- AddCommand (Command.NextView, () => ProcessMoveNextView ());
+ AddCommand (Command.Tab, () => ProcessTab ());
+ AddCommand (Command.BackTab, () => ProcessBackTab ());
+ AddCommand (Command.NextView, () => ProcessMoveNextView ());
AddCommand (Command.PreviousView, () => ProcessMovePreviousView ());
AddCommand (Command.Undo, () => {
Undo ();
@@ -2181,100 +2181,100 @@ public class TextView : View {
});
// Default keybindings for this view
- KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+ KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
KeyBindings.Add (KeyCode.V | KeyCode.CtrlMask, Command.PageDown);
KeyBindings.Add (KeyCode.PageDown | KeyCode.ShiftMask, Command.PageDownExtend);
- KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+ KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
KeyBindings.Add ('V' + KeyCode.AltMask, Command.PageUp);
KeyBindings.Add (KeyCode.PageUp | KeyCode.ShiftMask, Command.PageUpExtend);
KeyBindings.Add (KeyCode.N | KeyCode.CtrlMask, Command.LineDown);
- KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+ KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
KeyBindings.Add (KeyCode.CursorDown | KeyCode.ShiftMask, Command.LineDownExtend);
KeyBindings.Add (KeyCode.P | KeyCode.CtrlMask, Command.LineUp);
- KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+ KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
KeyBindings.Add (KeyCode.CursorUp | KeyCode.ShiftMask, Command.LineUpExtend);
KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
- KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+ KeyBindings.Add (KeyCode.CursorRight, Command.Right);
KeyBindings.Add (KeyCode.CursorRight | KeyCode.ShiftMask, Command.RightExtend);
KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
- KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
+ KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
KeyBindings.Add (KeyCode.CursorLeft | KeyCode.ShiftMask, Command.LeftExtend);
KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
- KeyBindings.Add (KeyCode.Home, Command.StartOfLine);
+ KeyBindings.Add (KeyCode.Home, Command.StartOfLine);
KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.StartOfLine);
KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.StartOfLineExtend);
- KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
+ KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
- KeyBindings.Add (KeyCode.End, Command.EndOfLine);
+ KeyBindings.Add (KeyCode.End, Command.EndOfLine);
KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.EndOfLine);
KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.EndOfLineExtend);
- KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); // kill-to-end
+ KeyBindings.Add (KeyCode.K | KeyCode.CtrlMask, Command.CutToEndLine); // kill-to-end
KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToEndLine); // kill-to-end
- KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); // kill-to-start
+ KeyBindings.Add (KeyCode.K | KeyCode.AltMask, Command.CutToStartLine); // kill-to-start
KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.CutToStartLine); // kill-to-start
- KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Paste); // Control-y, yank
+ KeyBindings.Add (KeyCode.Y | KeyCode.CtrlMask, Command.Paste); // Control-y, yank
KeyBindings.Add (KeyCode.Space | KeyCode.CtrlMask, Command.ToggleExtend);
- KeyBindings.Add ('C' + KeyCode.AltMask, Command.Copy);
+ KeyBindings.Add ('C' + KeyCode.AltMask, Command.Copy);
KeyBindings.Add (KeyCode.C | KeyCode.CtrlMask, Command.Copy);
- KeyBindings.Add ('W' + KeyCode.AltMask, Command.Cut);
+ KeyBindings.Add ('W' + KeyCode.AltMask, Command.Cut);
KeyBindings.Add (KeyCode.W | KeyCode.CtrlMask, Command.Cut);
KeyBindings.Add (KeyCode.X | KeyCode.CtrlMask, Command.Cut);
KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask, Command.WordLeft);
- KeyBindings.Add ('B' + KeyCode.AltMask, Command.WordLeft);
+ KeyBindings.Add ('B' + KeyCode.AltMask, Command.WordLeft);
KeyBindings.Add (KeyCode.CursorLeft | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordLeftExtend);
KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.WordRight);
- KeyBindings.Add ('F' + KeyCode.AltMask, Command.WordRight);
+ KeyBindings.Add ('F' + KeyCode.AltMask, Command.WordRight);
KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.WordRightExtend);
- KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards); // kill-word-forwards
- KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); // kill-word-backwards
+ KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards); // kill-word-forwards
+ KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards); // kill-word-backwards
// BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept).
- KeyBindings.Add (KeyCode.Enter, Command.NewLine);
- KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.BottomEnd);
- KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.BottomEndExtend);
- KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.TopHome);
+ KeyBindings.Add (KeyCode.Enter, Command.NewLine);
+ KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask, Command.BottomEnd);
+ KeyBindings.Add (KeyCode.End | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.BottomEndExtend);
+ KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask, Command.TopHome);
KeyBindings.Add (KeyCode.Home | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.TopHomeExtend);
- KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
- KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite);
- KeyBindings.Add (KeyCode.Tab, Command.Tab);
- KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.BackTab);
+ KeyBindings.Add (KeyCode.T | KeyCode.CtrlMask, Command.SelectAll);
+ KeyBindings.Add (KeyCode.Insert, Command.ToggleOverwrite);
+ KeyBindings.Add (KeyCode.Tab, Command.Tab);
+ KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.BackTab);
- KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextView);
+ KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextView);
KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextView);
KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.PreviousView);
- KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousView);
+ KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousView);
KeyBindings.Add (KeyCode.Z | KeyCode.CtrlMask, Command.Undo);
KeyBindings.Add (KeyCode.R | KeyCode.CtrlMask, Command.Redo);
- KeyBindings.Add (KeyCode.G | KeyCode.CtrlMask, Command.DeleteAll);
+ KeyBindings.Add (KeyCode.G | KeyCode.CtrlMask, Command.DeleteAll);
KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask | KeyCode.ShiftMask, Command.DeleteAll);
_currentCulture = Thread.CurrentThread.CurrentUICulture;
@@ -3183,7 +3183,7 @@ public class TextView : View {
foreach (char ch in toAdd) {
Key key;
try {
- key = new Key(ch);
+ key = new Key (ch);
} catch (Exception) {
throw new ArgumentException ($"Cannot insert character '{ch}' because it does not map to a Key");
}
@@ -3363,8 +3363,8 @@ public class TextView : View {
var offB = OffSetBackground ();
var line = GetCurrentLine ();
var need = NeedsDisplay || _wrapNeeded || !Used;
- var tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
- var dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth);
+ var tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
+ var dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth);
if (!_wordWrap && CurrentColumn < _leftColumn) {
_leftColumn = CurrentColumn;
need = true;
diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs
index e32eef6c8..d19cb7c95 100644
--- a/Terminal.Gui/Views/TileView.cs
+++ b/Terminal.Gui/Views/TileView.cs
@@ -301,7 +301,7 @@ namespace Terminal.Gui {
/// Overridden so no Frames get drawn (BUGBUG: v2 fix this hack)
///
///
- public override bool OnDrawFrames ()
+ public override bool OnDrawAdornments ()
{
return false;
}
diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs
index ee45f1ad1..bc6e7c6fe 100644
--- a/Terminal.Gui/Views/TimeField.cs
+++ b/Terminal.Gui/Views/TimeField.cs
@@ -9,334 +9,394 @@ using System.Globalization;
using System.Linq;
using System.Text;
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+///
+/// Time editing
+///
+///
+/// The provides time editing functionality with mouse support.
+///
+public class TimeField : TextField {
+ TimeSpan _time;
+ bool _isShort;
+
+ int _longFieldLen = 8;
+ int _shortFieldLen = 5;
+ string _sepChar;
+ string _longFormat;
+ string _shortFormat;
+
+ int _fieldLen => _isShort ? _shortFieldLen : _longFieldLen;
+ string _format => _isShort ? _shortFormat : _longFormat;
+
///
- /// Time editing
+ /// TimeChanged event, raised when the Date has changed.
///
///
- /// The provides time editing functionality with mouse support.
+ /// This event is raised when the changes.
///
- public class TimeField : TextField {
- TimeSpan time;
- bool isShort;
+ ///
+ /// The passed is a containing the old value, new value, and format string.
+ ///
+ public event EventHandler> TimeChanged;
- int longFieldLen = 8;
- int shortFieldLen = 5;
- string sepChar;
- string longFormat;
- string shortFormat;
+ ///
+ /// Initializes a new instance of using positioning.
+ ///
+ /// The x coordinate.
+ /// The y coordinate.
+ /// Initial time.
+ /// If true, the seconds are hidden. Sets the property.
+ public TimeField (int x, int y, TimeSpan time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
+ {
+ SetInitialProperties (time, isShort);
+ }
- int fieldLen => isShort ? shortFieldLen : longFieldLen;
- string format => isShort ? shortFormat : longFormat;
+ ///
+ /// Initializes a new instance of using positioning.
+ ///
+ /// Initial time
+ public TimeField (TimeSpan time) : base (string.Empty)
+ {
+ Width = _fieldLen + 2;
+ SetInitialProperties (time);
+ }
- ///
- /// TimeChanged event, raised when the Date has changed.
- ///
- ///
- /// This event is raised when the changes.
- ///
- ///
- /// The passed is a containing the old value, new value, and format string.
- ///
- public event EventHandler> TimeChanged;
+ ///
+ /// Initializes a new instance of using positioning.
+ ///
+ public TimeField () : this (time: TimeSpan.MinValue) { }
- ///
- /// Initializes a new instance of using positioning.
- ///
- /// The x coordinate.
- /// The y coordinate.
- /// Initial time.
- /// If true, the seconds are hidden. Sets the property.
- public TimeField (int x, int y, TimeSpan time, bool isShort = false) : base (x, y, isShort ? 7 : 10, "")
- {
- Initialize (time, isShort);
- }
+ void SetInitialProperties (TimeSpan time, bool isShort = false)
+ {
+ CultureInfo cultureInfo = CultureInfo.CurrentCulture;
+ _sepChar = cultureInfo.DateTimeFormat.TimeSeparator;
+ _longFormat = $" hh\\{_sepChar}mm\\{_sepChar}ss";
+ _shortFormat = $" hh\\{_sepChar}mm";
+ this._isShort = isShort;
+ Time = time;
+ CursorPosition = 1;
+ TextChanging += TextField_TextChanging;
- ///
- /// Initializes a new instance of using positioning.
- ///
- /// Initial time
- public TimeField (TimeSpan time) : base (string.Empty)
- {
- Width = fieldLen + 2;
- Initialize (time);
- }
+ // Things this view knows how to do
+ AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
+ AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
+ AddCommand (Command.LeftHome, () => MoveHome ());
+ AddCommand (Command.Left, () => MoveLeft ());
+ AddCommand (Command.RightEnd, () => MoveEnd ());
+ AddCommand (Command.Right, () => MoveRight ());
- ///
- /// Initializes a new instance of using positioning.
- ///
- public TimeField () : this (time: TimeSpan.MinValue) { }
+ // Default keybindings for this view
+ KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
+ KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
- void Initialize (TimeSpan time, bool isShort = false)
- {
- CultureInfo cultureInfo = CultureInfo.CurrentCulture;
- sepChar = cultureInfo.DateTimeFormat.TimeSeparator;
- longFormat = $" hh\\{sepChar}mm\\{sepChar}ss";
- shortFormat = $" hh\\{sepChar}mm";
- this.isShort = isShort;
- Time = time;
- CursorPosition = 1;
- TextChanged += TextField_TextChanged;
+ KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
+ KeyBindings.Add (Key.D.WithAlt, Command.DeleteCharLeft);
- // Things this view knows how to do
- AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
- AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
- AddCommand (Command.LeftHome, () => MoveHome ());
- AddCommand (Command.Left, () => MoveLeft ());
- AddCommand (Command.RightEnd, () => MoveEnd ());
- AddCommand (Command.Right, () => MoveRight ());
+ KeyBindings.Add (Key.Home, Command.LeftHome);
+ KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome);
- // Default keybindings for this view
- KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
- KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
+ KeyBindings.Add (Key.CursorLeft, Command.Left);
+ KeyBindings.Add (Key.B.WithCtrl, Command.Left);
- KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft);
- KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
+ KeyBindings.Add (Key.End, Command.RightEnd);
+ KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
- KeyBindings.Add (KeyCode.Home, Command.LeftHome);
- KeyBindings.Add (KeyCode.A | KeyCode.CtrlMask, Command.LeftHome);
+ KeyBindings.Add (Key.CursorRight, Command.Right);
+ KeyBindings.Add (Key.F.WithCtrl, Command.Right);
+ }
- KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
- KeyBindings.Add (KeyCode.B | KeyCode.CtrlMask, Command.Left);
-
- KeyBindings.Add (KeyCode.End, Command.RightEnd);
- KeyBindings.Add (KeyCode.E | KeyCode.CtrlMask, Command.RightEnd);
-
- KeyBindings.Add (KeyCode.CursorRight, Command.Right);
- KeyBindings.Add (KeyCode.F | KeyCode.CtrlMask, Command.Right);
- }
-
- void TextField_TextChanged (object sender, TextChangedEventArgs e)
- {
- try {
- if (!TimeSpan.TryParseExact (Text.Trim (), format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result))
- Text = e.OldValue;
- } catch (Exception) {
- Text = e.OldValue;
- }
- }
-
- ///
- /// Gets or sets the time of the .
- ///
- ///
- ///
- public TimeSpan Time {
- get {
- return time;
- }
- set {
- if (ReadOnly)
- return;
-
- var oldTime = time;
- time = value;
- this.Text = " " + value.ToString (format.Trim ());
- var args = new DateTimeEventArgs (oldTime, value, format);
- if (oldTime != value) {
- OnTimeChanged (args);
+ void TextField_TextChanging (object sender, TextChangingEventArgs e)
+ {
+ try {
+ int spaces = 0;
+ for (int i = 0; i < e.NewText.Length; i++) {
+ if (e.NewText [i] == ' ') {
+ spaces++;
+ } else {
+ break;
}
}
+ spaces += _fieldLen;
+ string trimedText = e.NewText [..spaces];
+ spaces -= _fieldLen;
+ trimedText = trimedText.Replace (new string (' ', spaces), " ");
+ if (trimedText != e.NewText) {
+ e.NewText = trimedText;
+ }
+ if (!TimeSpan.TryParseExact (e.NewText.Trim (), _format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result)) {
+ e.Cancel = true;
+ }
+ AdjCursorPosition (CursorPosition, true);
+ } catch (Exception) {
+ e.Cancel = true;
}
+ }
- ///
- /// Get or sets whether uses the short or long time format.
- ///
- public bool IsShortFormat {
- get => isShort;
- set {
- isShort = value;
- if (isShort)
- Width = 7;
- else
- Width = 10;
- var ro = ReadOnly;
- if (ro)
- ReadOnly = false;
- SetText (Text);
- ReadOnly = ro;
- SetNeedsDisplay ();
- }
+ ///
+ /// Gets or sets the time of the .
+ ///
+ ///
+ ///
+ public TimeSpan Time {
+ get {
+ return _time;
}
-
- ///
- public override int CursorPosition {
- get => base.CursorPosition;
- set {
- base.CursorPosition = Math.Max (Math.Min (value, fieldLen), 1);
- }
- }
-
- bool SetText (Rune key)
- {
- var text = Text.EnumerateRunes ().ToList ();
- var newText = text.GetRange (0, CursorPosition);
- newText.Add (key);
- if (CursorPosition < fieldLen)
- newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
- return SetText (StringExtensions.ToString (newText));
- }
-
- bool SetText (string text)
- {
- if (string.IsNullOrEmpty (text)) {
- return false;
- }
-
- string [] vals = text.Split (sepChar);
- bool isValidTime = true;
- int hour = Int32.Parse (vals [0]);
- int minute = Int32.Parse (vals [1]);
- int second = isShort ? 0 : vals.Length > 2 ? Int32.Parse (vals [2].ToString ()) : 0;
- if (hour < 0) {
- isValidTime = false;
- hour = 0;
- vals [0] = "0";
- } else if (hour > 23) {
- isValidTime = false;
- hour = 23;
- vals [0] = "23";
- }
- if (minute < 0) {
- isValidTime = false;
- minute = 0;
- vals [1] = "0";
- } else if (minute > 59) {
- isValidTime = false;
- minute = 59;
- vals [1] = "59";
- }
- if (second < 0) {
- isValidTime = false;
- second = 0;
- vals [2] = "0";
- } else if (second > 59) {
- isValidTime = false;
- second = 59;
- vals [2] = "59";
- }
- string t = isShort ? $" {hour,2:00}{sepChar}{minute,2:00}" : $" {hour,2:00}{sepChar}{minute,2:00}{sepChar}{second,2:00}";
-
- if (!TimeSpan.TryParseExact (t.Trim (), format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result) ||
- !isValidTime)
- return false;
- Time = result;
- return true;
- }
-
- void IncCursorPosition ()
- {
- if (CursorPosition == fieldLen)
+ set {
+ if (ReadOnly)
return;
- if (Text [++CursorPosition] == sepChar.ToCharArray () [0])
- CursorPosition++;
- }
- void DecCursorPosition ()
- {
- if (CursorPosition == 1)
- return;
- if (Text [--CursorPosition] == sepChar.ToCharArray () [0])
- CursorPosition--;
- }
-
- void AdjCursorPosition ()
- {
- if (Text [CursorPosition] == sepChar.ToCharArray () [0])
- CursorPosition++;
- }
-
- ///
- public override bool OnProcessKeyDown (Key a)
- {
- // Ignore non-numeric characters.
- if (a.KeyCode is >= (KeyCode)(int)KeyCode.D0 and <= (KeyCode)(int)KeyCode.D9) {
- if (!ReadOnly) {
- if (SetText ((Rune)a)) {
- IncCursorPosition ();
- }
- }
- return true;
+ var oldTime = _time;
+ _time = value;
+ this.Text = " " + value.ToString (_format.Trim ());
+ var args = new DateTimeEventArgs (oldTime, value, _format);
+ if (oldTime != value) {
+ OnTimeChanged (args);
}
+ }
+ }
- if (a.IsKeyCodeAtoZ) {
- return true;
- }
-
+ ///
+ /// Get or sets whether uses the short or long time format.
+ ///
+ public bool IsShortFormat {
+ get => _isShort;
+ set {
+ _isShort = value;
+ if (_isShort)
+ Width = 7;
+ else
+ Width = 10;
+ var ro = ReadOnly;
+ if (ro)
+ ReadOnly = false;
+ SetText (Text);
+ ReadOnly = ro;
+ SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ public override int CursorPosition {
+ get => base.CursorPosition;
+ set {
+ base.CursorPosition = Math.Max (Math.Min (value, _fieldLen), 1);
+ }
+ }
+
+ bool SetText (Rune key)
+ {
+ var text = Text.EnumerateRunes ().ToList ();
+ var newText = text.GetRange (0, CursorPosition);
+ newText.Add (key);
+ if (CursorPosition < _fieldLen)
+ newText = [.. newText, .. text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))];
+ return SetText (StringExtensions.ToString (newText));
+ }
+
+ bool SetText (string text)
+ {
+ if (string.IsNullOrEmpty (text)) {
return false;
}
- bool MoveRight ()
- {
- IncCursorPosition ();
- return true;
+ text = NormalizeFormat (text);
+ string [] vals = text.Split (_sepChar);
+ bool isValidTime = true;
+ int hour = Int32.Parse (vals [0]);
+ int minute = Int32.Parse (vals [1]);
+ int second = _isShort ? 0 : vals.Length > 2 ? Int32.Parse (vals [2]) : 0;
+ if (hour < 0) {
+ isValidTime = false;
+ hour = 0;
+ vals [0] = "0";
+ } else if (hour > 23) {
+ isValidTime = false;
+ hour = 23;
+ vals [0] = "23";
+ }
+ if (minute < 0) {
+ isValidTime = false;
+ minute = 0;
+ vals [1] = "0";
+ } else if (minute > 59) {
+ isValidTime = false;
+ minute = 59;
+ vals [1] = "59";
+ }
+ if (second < 0) {
+ isValidTime = false;
+ second = 0;
+ vals [2] = "0";
+ } else if (second > 59) {
+ isValidTime = false;
+ second = 59;
+ vals [2] = "59";
+ }
+ string t = _isShort ? $" {hour,2:00}{_sepChar}{minute,2:00}" : $" {hour,2:00}{_sepChar}{minute,2:00}{_sepChar}{second,2:00}";
+
+ if (!TimeSpan.TryParseExact (t.Trim (), _format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result) ||
+ !isValidTime) {
+ return false;
+ }
+ Time = result;
+ return true;
+ }
+
+ string NormalizeFormat (string text, string fmt = null, string sepChar = null)
+ {
+ if (string.IsNullOrEmpty (fmt)) {
+ fmt = _format;
+ }
+ fmt = fmt.Replace ("\\", "");
+ if (string.IsNullOrEmpty (sepChar)) {
+ sepChar = _sepChar;
+ }
+ if (fmt.Length != text.Length) {
+ return text;
}
- new bool MoveEnd ()
- {
- CursorPosition = fieldLen;
- return true;
+ var fmtText = text.ToCharArray ();
+ for (int i = 0; i < text.Length; i++) {
+ var c = fmt [i];
+ if (c.ToString () == sepChar && text [i].ToString () != sepChar) {
+ fmtText [i] = c;
+ }
}
- bool MoveLeft ()
- {
- DecCursorPosition ();
- return true;
- }
+ return new string (fmtText);
+ }
- bool MoveHome ()
- {
- // Home, C-A
+ void IncCursorPosition ()
+ {
+ if (CursorPosition >= _fieldLen) {
+ CursorPosition = _fieldLen;
+ return;
+ }
+ CursorPosition++;
+ AdjCursorPosition (CursorPosition);
+ }
+
+ void DecCursorPosition ()
+ {
+ if (CursorPosition <= 1) {
CursorPosition = 1;
- return true;
- }
-
- ///
- public override void DeleteCharLeft (bool useOldCursorPos = true)
- {
- if (ReadOnly)
- return;
-
- SetText ((Rune)'0');
- DecCursorPosition ();
return;
}
+ CursorPosition--;
+ AdjCursorPosition (CursorPosition, false);
+ }
- ///
- public override void DeleteCharRight ()
- {
- if (ReadOnly)
- return;
-
- SetText ((Rune)'0');
- return;
+ void AdjCursorPosition (int point, bool increment = true)
+ {
+ var newPoint = point;
+ if (point > _fieldLen) {
+ newPoint = _fieldLen;
+ }
+ if (point < 1) {
+ newPoint = 1;
+ }
+ if (newPoint != point) {
+ CursorPosition = newPoint;
}
- ///
- public override bool MouseEvent (MouseEvent ev)
- {
- if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked))
- return false;
- if (!HasFocus)
- SetFocus ();
-
- var point = ev.X;
- if (point > fieldLen)
- point = fieldLen;
- if (point < 1)
- point = 1;
- CursorPosition = point;
- AdjCursorPosition ();
- return true;
- }
-
- ///
- /// Event firing method that invokes the event.
- ///
- /// The event arguments
- public virtual void OnTimeChanged (DateTimeEventArgs args)
- {
- TimeChanged?.Invoke (this, args);
+ while (Text [CursorPosition] == _sepChar [0]) {
+ if (increment) {
+ CursorPosition++;
+ } else {
+ CursorPosition--;
+ }
}
}
-}
\ No newline at end of file
+
+ ///
+ public override bool OnProcessKeyDown (Key a)
+ {
+ // Ignore non-numeric characters.
+ if (a.KeyCode is >= (KeyCode)(int)KeyCode.D0 and <= (KeyCode)(int)KeyCode.D9) {
+ if (!ReadOnly) {
+ if (SetText ((Rune)a)) {
+ IncCursorPosition ();
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ bool MoveRight ()
+ {
+ ClearAllSelection ();
+ IncCursorPosition ();
+ return true;
+ }
+
+ new bool MoveEnd ()
+ {
+ ClearAllSelection ();
+ CursorPosition = _fieldLen;
+ return true;
+ }
+
+ bool MoveLeft ()
+ {
+ ClearAllSelection ();
+ DecCursorPosition ();
+ return true;
+ }
+
+ bool MoveHome ()
+ {
+ // Home, C-A
+ ClearAllSelection ();
+ CursorPosition = 1;
+ return true;
+ }
+
+ ///
+ public override void DeleteCharLeft (bool useOldCursorPos = true)
+ {
+ if (ReadOnly) {
+ return;
+ }
+
+ ClearAllSelection ();
+ SetText ((Rune)'0');
+ DecCursorPosition ();
+ return;
+ }
+
+ ///
+ public override void DeleteCharRight ()
+ {
+ if (ReadOnly) {
+ return;
+ }
+
+ ClearAllSelection ();
+ SetText ((Rune)'0');
+ return;
+ }
+
+ ///
+ public override bool MouseEvent (MouseEvent ev)
+ {
+ var result = base.MouseEvent (ev);
+
+ if (result && SelectedLength == 0 && ev.Flags.HasFlag (MouseFlags.Button1Pressed)) {
+ int point = ev.X;
+ AdjCursorPosition (point, true);
+ }
+ return result;
+ }
+
+ ///
+ /// Event firing method that invokes the event.
+ ///
+ /// The event arguments
+ public virtual void OnTimeChanged (DateTimeEventArgs args)
+ {
+ TimeChanged?.Invoke (this, args);
+ }
+}
diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs
index 5f7e20d9b..5dc62761d 100644
--- a/Terminal.Gui/Views/Toplevel.cs
+++ b/Terminal.Gui/Views/Toplevel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -12,7 +12,7 @@ namespace Terminal.Gui;
///
///
/// Toplevels can run as modal (popup) views, started by calling
-/// .
+/// .
/// They return control to the caller when has
/// been called (which sets the property to false).
///
@@ -22,12 +22,10 @@ namespace Terminal.Gui;
/// The application Toplevel can be accessed via . Additional
/// Toplevels can be created
/// and run (e.g. s. To run a Toplevel, create the and
-/// call .
+/// call .
///
///
public partial class Toplevel : View {
- internal static Point? _dragPosition;
- Point _startGrabPoint;
///
/// Initializes a new instance of the class with the specified
@@ -253,7 +251,7 @@ public partial class Toplevel : View {
void SetInitialProperties ()
{
- ColorScheme = Colors.TopLevel;
+ ColorScheme = Colors.ColorSchemes ["TopLevel"];
Application.GrabbingMouse += Application_GrabbingMouse;
Application.UnGrabbingMouse += Application_UnGrabbingMouse;
@@ -292,64 +290,32 @@ public partial class Toplevel : View {
Application.Refresh ();
return true;
});
- AddCommand (Command.Accept, () => {
- // TODO: Perhaps all views should support the concept of being default?
- // TODO: It's bad that Toplevel is tightly coupled with Button
- if (Subviews.FirstOrDefault (v => v is Button && ((Button)v).IsDefault && ((Button)v).Enabled) is Button defaultBtn) {
- defaultBtn.InvokeCommand (Command.Accept);
- return true;
- }
- return false;
- });
+
// Default keybindings for this view
- KeyBindings.Add ((KeyCode)Application.QuitKey, Command.QuitToplevel);
+ KeyBindings.Add (Application.QuitKey, Command.QuitToplevel);
- KeyBindings.Add (KeyCode.CursorRight, Command.NextView);
- KeyBindings.Add (KeyCode.CursorDown, Command.NextView);
- KeyBindings.Add (KeyCode.CursorLeft, Command.PreviousView);
- KeyBindings.Add (KeyCode.CursorUp, Command.PreviousView);
- KeyBindings.Add (KeyCode.CursorDown, Command.NextView);
- KeyBindings.Add (KeyCode.CursorLeft, Command.PreviousView);
- KeyBindings.Add (KeyCode.CursorUp, Command.PreviousView);
+ KeyBindings.Add (Key.CursorRight, Command.NextView);
+ KeyBindings.Add (Key.CursorDown, Command.NextView);
+ KeyBindings.Add (Key.CursorLeft, Command.PreviousView);
+ KeyBindings.Add (Key.CursorUp, Command.PreviousView);
- KeyBindings.Add (KeyCode.Tab, Command.NextView);
- KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.PreviousView);
- KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextViewOrTop);
- KeyBindings.Add (KeyCode.Tab, Command.NextView);
- KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.PreviousView);
- KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextViewOrTop);
- KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.PreviousViewOrTop);
+ KeyBindings.Add (Key.Tab, Command.NextView);
+ KeyBindings.Add (Key.Tab.WithShift, Command.PreviousView);
+ KeyBindings.Add (Key.Tab.WithCtrl, Command.NextViewOrTop);
+ KeyBindings.Add (Key.Tab.WithShift.WithCtrl, Command.PreviousViewOrTop);
- KeyBindings.Add (KeyCode.F5, Command.Refresh);
- KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
- KeyBindings.Add (KeyCode.F5, Command.Refresh);
- KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
- KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
+ KeyBindings.Add (Key.F5, Command.Refresh);
+ KeyBindings.Add (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
+ KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
#if UNIX_KEY_BINDINGS
- KeyBindings.Add (Key.Z | Key.CtrlMask, Command.Suspend);
- KeyBindings.Add (Key.L | Key.CtrlMask, Command.Refresh);// Unix
- KeyBindings.Add (Key.F | Key.CtrlMask, Command.NextView);// Unix
- KeyBindings.Add (Key.I | Key.CtrlMask, Command.NextView); // Unix
- KeyBindings.Add (Key.B | Key.CtrlMask, Command.PreviousView);// Unix
+ KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
+ KeyBindings.Add (Key.L.WithCtrl, Command.Refresh);// Unix
+ KeyBindings.Add (Key.F.WithCtrl, Command.NextView);// Unix
+ KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix
+ KeyBindings.Add (Key.B.WithCtrl, Command.PreviousView);// Unix
#endif
- // This enables the default button to be activated by the Enter key.
- KeyBindings.Add (KeyCode.Enter, Command.Accept);
- }
-
- void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
- {
- if (Application.MouseGrabView == this && _dragPosition.HasValue) {
- e.Cancel = true;
- }
- }
-
- void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
- {
- if (Application.MouseGrabView == this && _dragPosition.HasValue) {
- e.Cancel = true;
- }
}
///
@@ -363,7 +329,7 @@ public partial class Toplevel : View {
///
public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
{
- KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
+ KeyBindings.Replace (e.OldKey, e.NewKey);
AlternateForwardKeyChanged?.Invoke (this, e);
}
@@ -378,7 +344,7 @@ public partial class Toplevel : View {
///
public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e)
{
- KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
+ KeyBindings.Replace (e.OldKey, e.NewKey);
AlternateBackwardKeyChanged?.Invoke (this, e);
}
@@ -393,16 +359,10 @@ public partial class Toplevel : View {
///
public virtual void OnQuitKeyChanged (KeyChangedEventArgs e)
{
- KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
+ KeyBindings.Replace (e.OldKey, e.NewKey);
QuitKeyChanged?.Invoke (this, e);
}
- ///
- /// Convenience factory method that creates a new Toplevel with the current terminal dimensions.
- ///
- /// The created Toplevel.
- public static Toplevel Create () => new (new Rect (0, 0, Driver.Cols, Driver.Rows));
-
void MovePreviousViewOrTop ()
{
if (Application.OverlappedTop == null) {
@@ -609,7 +569,7 @@ public partial class Toplevel : View {
superView = top.SuperView;
}
if (superView.Margin != null && superView == top.SuperView) {
- maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
+ maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right;
}
if (top.Frame.Width <= maxWidth) {
nx = Math.Max (targetX, 0);
@@ -656,7 +616,7 @@ public partial class Toplevel : View {
maxWidth = statusVisible ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height;
}
if (superView.Margin != null && superView == top.SuperView) {
- maxWidth -= superView.GetFramesThickness ().Top + superView.GetFramesThickness ().Bottom;
+ maxWidth -= superView.GetAdornmentsThickness ().Top + superView.GetAdornmentsThickness ().Bottom;
}
ny = Math.Min (ny, maxWidth);
if (top.Frame.Height <= maxWidth) {
@@ -694,7 +654,7 @@ public partial class Toplevel : View {
var layoutSubviews = false;
var maxWidth = 0;
if (superView.Margin != null && superView == top.SuperView) {
- maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
+ maxWidth -= superView.GetAdornmentsThickness ().Left + superView.GetAdornmentsThickness ().Right;
}
if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal || top?.SuperView == null && top.IsOverlapped)
// BUGBUG: Prevously PositionToplevel required LayotuStyle.Computed
@@ -780,6 +740,23 @@ public partial class Toplevel : View {
return false;
}
+ internal static Point? _dragPosition;
+ Point _startGrabPoint;
+
+ void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
+ {
+ if (Application.MouseGrabView == this && _dragPosition.HasValue) {
+ e.Cancel = true;
+ }
+ }
+
+ void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
+ {
+ if (Application.MouseGrabView == this && _dragPosition.HasValue) {
+ e.Cancel = true;
+ }
+ }
+
///
public override bool MouseEvent (MouseEvent mouseEvent)
{
diff --git a/Terminal.Gui/Views/ToplevelOverlapped.cs b/Terminal.Gui/Views/ToplevelOverlapped.cs
index 4593f7bed..77025948c 100644
--- a/Terminal.Gui/Views/ToplevelOverlapped.cs
+++ b/Terminal.Gui/Views/ToplevelOverlapped.cs
@@ -42,7 +42,7 @@ public static partial class Application {
///
public static Toplevel OverlappedTop {
get {
- if (Top.IsOverlappedContainer) {
+ if (Top is { IsOverlappedContainer: true }) {
return Top;
}
return null;
diff --git a/Terminal.Gui/Views/TreeView/TreeNode.cs b/Terminal.Gui/Views/TreeView/TreeNode.cs
index 5ae07ae19..a8769b6e9 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 {
///
- /// Interface to implement when you want the regular (non generic)
- /// to automatically determine children for your class (without having to specify
- /// an )
+ /// Text to display when rendering the node
///
- public interface ITreeNode {
- ///
- /// Text to display when rendering the node
- ///
- string Text { get; set; }
-
- ///
- /// 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; }
- }
+ string Text { get; set; }
///
- /// Simple class for representing nodes, use with regular (non generic) .
+ /// The children of your class which should be rendered underneath it when expanded
///
- public class TreeNode : ITreeNode {
- ///
- /// Children of the current node
- ///
- ///
- public virtual IList Children { get; set; } = new List ();
+ ///
+ IList Children { get; }
- ///
- /// Text to display in tree node for current entry
- ///
- ///
- public virtual string Text { get; set; }
+ ///
+ /// Optionally allows you to store some custom data/class here.
+ ///
+ object Tag { get; set; }
+}
- ///
- /// Optionally allows you to store some custom data/class here.
- ///
- public object Tag { get; set; }
+///
+/// Simple class for representing nodes, use with regular (non generic) .
+///
+public class TreeNode : ITreeNode {
- ///
- /// returns
- ///
- ///
- public override string ToString ()
- {
- return Text ?? "Unamed Node";
- }
+ ///
+ /// Initialises a new instance with no
+ ///
+ public TreeNode () { }
- ///
- /// Initialises a new instance with no
- ///
- public TreeNode ()
- {
+ ///
+ /// Initialises a new instance and sets starting
+ ///
+ public TreeNode (string text) => Text = text;
- }
- ///
- /// Initialises a new instance and sets starting
- ///
- public TreeNode (string text)
- {
- Text = text;
- }
- }
+ ///
+ /// Children of the current node
+ ///
+ ///
+ public virtual IList Children { get; set; } = new List ();
+
+ ///
+ /// Text to display in tree node for current entry
+ ///
+ ///
+ public virtual string Text { get; set; }
+
+ ///
+ /// Optionally allows you to store some custom data/class here.
+ ///
+ public object Tag { get; set; }
+
+ ///
+ /// 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 b5ca8f482..9632c81b8 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 {
+ ///
+ /// Contains options for changing how the tree is rendered.
+ ///
+ TreeStyle Style { get; set; }
///
- /// Interface for all non generic members of .
- ///
- /// See TreeView Deep Dive for more information.
+ /// Removes all objects from the tree and clears selection.
///
- public interface ITreeView {
- ///
- /// Contains options for changing how the tree is rendered.
- ///
- TreeStyle Style { get; set; }
+ void ClearObjects ();
- ///
- /// 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 ();
+}
- ///
- /// 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 {
+
+ ///
+ /// 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";
+
+ ///
+ /// 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);
}
///
- /// Convenience implementation of generic for any tree were all nodes
- /// implement .
- ///
- /// See TreeView Deep Dive for more information.
+ /// Initialises .Creates a new tree view with absolute
+ /// positioning. Use to set set root
+ /// objects for the tree.
///
- public class TreeView : TreeView {
+ public TreeView (ITreeBuilder builder) : this () => TreeBuilder = builder;
- ///
- /// 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");
+ ///
+ /// Determines how sub branches of the tree are dynamically built at runtime as the user
+ /// expands root nodes.
+ ///
+ ///
+ public ITreeBuilder TreeBuilder { get; set; }
+
+ ///
+ /// 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));
+ }
+ }
+ }
+
+ // 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;
+ }
}
}
///
- /// Hierarchical tree view with expandable branches. Branch objects are dynamically determined
- /// when expanded using a user defined .
- ///
- /// See TreeView Deep Dive for more information.
+ /// Mouse event to trigger .
+ /// Defaults to double click ().
+ /// Set to null to disable this feature.
///
- public class TreeView : View, ITreeView where T : class {
- private int scrollOffsetVertical;
- private int scrollOffsetHorizontal;
+ ///
+ public MouseFlags? ObjectActivationButton { get; set; } = MouseFlags.Button1DoubleClicked;
- ///
- /// Determines how sub branches of the tree are dynamically built at runtime as the user
- /// expands root nodes.
- ///
- ///
- public ITreeBuilder TreeBuilder { get; set; }
+ ///
+ /// 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; }
- ///
- /// private variable for
- ///
- T selectedObject;
+ ///
+ /// The root objects in the tree, note that this collection is of root objects only.
+ ///
+ public IEnumerable Objects => roots.Keys;
- ///
- /// Contains options for changing how the tree is rendered.
- ///
- public TreeStyle Style { get; set; } = new TreeStyle ();
+ ///
+ /// 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 ();
- ///
- /// True to allow multiple objects to be selected at once.
- ///
- ///
- public bool MultiSelect { get; set; } = true;
+ ///
+ /// 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);
+ }
- ///
- /// Maximum number of nodes that can be expanded in any given branch.
- ///
- public int MaxDepth { get; set; } = 100;
+ ///
+ /// 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);
+ }
- ///
- /// 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 current number of rows in the tree (ignoring the controls bounds).
+ ///
+ public int ContentHeight => BuildLineMap ().Count ();
- ///
- /// 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;
+ ///
+ /// 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 (!ReferenceEquals (oldValue, value)) {
- OnSelectionChanged (new SelectionChangedEventArgs (this, oldValue, value));
+ ///
+ /// 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);
}
}
}
+ }
- ///
- /// This event is raised when an object is activated e.g. by double clicking or
- /// pressing .
- ///
- public event EventHandler> ObjectActivated;
+ ///
+ /// Gets the that searches the collection as
+ /// the user types.
+ ///
+ public CollectionNavigator KeystrokeNavigator { get; } = new ();
- // 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;
- }
+ ///
+ /// Contains options for changing how the tree is rendered.
+ ///
+ public TreeStyle Style { get; set; } = new ();
+
+ ///
+ /// Removes all objects from the tree and clears .
+ ///
+ public void ClearObjects ()
+ {
+ SelectedObject = default;
+ multiSelectedRegions.Clear ();
+ roots = new Dictionary> ();
+ 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 ();
+ }
+
+ 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 ();
+ }
+ }
+
+ ///
+ /// 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 (Equals (SelectedObject, o)) {
+ SelectedObject = default;
}
}
+ }
- ///
- /// Mouse event to trigger .
- /// Defaults to double click ().
- /// Set to null to disable this feature.
- ///
- ///
- public MouseFlags? ObjectActivationButton { get; set; } = MouseFlags.Button1DoubleClicked;
+ ///
+ /// 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;
- ///
- /// 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);
- }
- }
-
- ///
- /// 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 () ?? "";
-
- 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);
- }
- }
- }
- }
-
- ///
- /// 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 ();
- }
-
- return base.OnEnter (view);
- }
-
- ///
- /// Adds a new root level object unless it is already a root of the tree.
- ///
- ///
- public void AddObject (T o)
- {
+ foreach (var o in collection) {
if (!roots.ContainsKey (o)) {
roots.Add (o, new Branch (this, null, o));
- InvalidateLineMap ();
- SetNeedsDisplay ();
+ objectsAdded = true;
}
}
- ///
- /// Removes all objects from the tree and clears .
- ///
- public void ClearObjects ()
- {
- SelectedObject = default (T);
- multiSelectedRegions.Clear ();
- roots = new Dictionary> ();
+ if (objectsAdded) {
+ 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 ();
}
- ///
- /// 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 (Equals (SelectedObject, o)) {
- SelectedObject = default (T);
- }
+ ///
+ /// 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 ();
+ }
+
+ 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);
+
+ if (branch == null || !branch.IsExpanded) {
+ return new T [0];
+ }
+
+ 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) => ObjectToBranch (o)?.Parent?.Model;
+
+ ///
+ public override void OnDrawContent (Rect contentArea)
+ {
+ if (roots == null) {
+ return;
+ }
+
+ if (TreeBuilder == null) {
+ Move (0, 0);
+ Driver.AddStr (NoBuilderError);
+ return;
+ }
+
+ var map = BuildLineMap ();
+
+ 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 {
+
+ // Else clear the line to prevent stale symbols due to scrolling etc
+ Move (0, line);
+ Driver.SetAttribute (GetNormalColor ());
+ Driver.AddStr (new string (' ', Bounds.Width));
+ }
+ }
+ }
+
+ ///
+ /// 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;
}
}
- ///
- /// 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;
+ //object not found
+ return -1;
+ }
- foreach (var o in collection) {
- if (!roots.ContainsKey (o)) {
- roots.Add (o, new Branch (this, null, o));
- objectsAdded = true;
- }
- }
+ ///
+ /// 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 ();
- if (objectsAdded) {
- InvalidateLineMap ();
- SetNeedsDisplay ();
- }
+ if (map.Count == 0) {
+ return 0;
}
- ///
- /// 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 ();
- }
+ if (visible) {
- }
-
- ///
- /// 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 ();
- }
-
- 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);
-
- if (branch == null || !branch.IsExpanded) {
- return new T [0];
- }
-
- 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;
- }
-
- if (TreeBuilder == null) {
- Move (0, 0);
- Driver.AddStr (NoBuilderError);
- return;
- }
-
- var map = BuildLineMap ();
-
- for (int 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 {
-
- // Else clear the line to prevent stale symbols due to scrolling etc
- Move (0, line);
- Driver.SetAttribute (GetNormalColor ());
- Driver.AddStr (new string (' ', Bounds.Width));
- }
- }
- }
-
- ///
- /// 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;
- }
- }
-
- //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 ();
-
- if (map.Count == 0) {
+ //Somehow we managed to scroll off the end of the control
+ if (ScrollOffsetVertical >= map.Count) {
return 0;
}
- if (visible) {
-
- //Somehow we managed to scroll off the end of the control
- if (ScrollOffsetVertical >= map.Count) {
- return 0;
- }
-
- // If control has no height to it then there is no visible area for content
- if (Bounds.Height == 0) {
- return 0;
- }
-
- return map.Skip (ScrollOffsetVertical).Take (Bounds.Height).Max (b => b.GetWidth (Driver));
- } else {
-
- return map.Max (b => b.GetWidth (Driver));
+ // If control has no height to it then there is no visible area for content
+ if (Bounds.Height == 0) {
+ return 0;
}
+
+ return map.Skip (ScrollOffsetVertical).Take (Bounds.Height).Max (b => b.GetWidth (Driver));
}
+ return map.Max (b => b.GetWidth (Driver));
+ }
- ///
- /// 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;
- }
-
- List> toReturn = new List> ();
-
- foreach (var root in roots.Values) {
-
- var toAdd = AddToLineMap (root, false, out var isMatch);
- if (isMatch) {
- toReturn.AddRange (toAdd);
- }
- }
-
- cachedLineMap = new ReadOnlyCollection> (toReturn);
-
- // Update the collection used for search-typing
- KeystrokeNavigator.Collection = cachedLineMap.Select (b => AspectGetter (b.Model)).ToArray ();
+ ///
+ /// 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 bool IsFilterMatch (Branch branch)
- {
- return Filter?.IsMatch (branch.Model) ?? true;
+ var toReturn = new List> ();
+
+ foreach (var root in roots.Values) {
+
+ var toAdd = AddToLineMap (root, false, out var isMatch);
+ if (isMatch) {
+ toReturn.AddRange (toAdd);
+ }
}
- private IEnumerable> AddToLineMap (Branch currentBranch, bool parentMatches, out bool match)
- {
- bool weMatch = IsFilterMatch (currentBranch);
- bool anyChildMatches = false;
+ cachedLineMap = new ReadOnlyCollection> (toReturn);
- var toReturn = new List> ();
- var children = new List> ();
+ // Update the collection used for search-typing
+ KeystrokeNavigator.Collection = cachedLineMap.Select (b => AspectGetter (b.Model)).ToArray ();
+ return cachedLineMap;
+ }
- if (currentBranch.IsExpanded) {
- foreach (var subBranch in currentBranch.ChildBranches.Values) {
+ bool IsFilterMatch (Branch branch) => Filter?.IsMatch (branch.Model) ?? true;
- foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) {
+ IEnumerable> AddToLineMap (Branch currentBranch, bool parentMatches, out bool match)
+ {
+ var weMatch = IsFilterMatch (currentBranch);
+ var anyChildMatches = false;
- if (childMatch) {
- children.Add (sub);
- anyChildMatches = true;
- }
+ var toReturn = new List> ();
+ var children = new List> ();
+
+ if (currentBranch.IsExpanded) {
+ foreach (var subBranch in currentBranch.ChildBranches.Values) {
+
+ foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) {
+
+ if (childMatch) {
+ children.Add (sub);
+ anyChildMatches = true;
}
}
}
-
- if (parentMatches || weMatch || anyChildMatches) {
- match = true;
- toReturn.Add (currentBranch);
- } else {
- match = false;
- }
-
- toReturn.AddRange (children);
- return toReturn;
}
- ///
- /// Gets the that searches the collection as
- /// the user types.
- ///
- public CollectionNavigator KeystrokeNavigator { get; private set; } = new CollectionNavigator ();
+ if (parentMatches || weMatch || anyChildMatches) {
+ match = true;
+ toReturn.Add (currentBranch);
+ } else {
+ match = false;
+ }
- ///
- public override bool OnProcessKeyDown (Key keyEvent)
- {
- if (!Enabled) {
- return false;
- }
-
- 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;
- }
- }
- } finally {
- if (IsInitialized) {
- PositionCursor ();
- }
- }
+ 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 (o != null) {
- OnObjectActivated (new ObjectActivatedEventArgs (this, o));
+ // 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;
+ }
+ }
+ } 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));
+ return false;
+ }
- 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));
+ ///
+ /// 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);
+ ///
+ ///
+ /// 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;
}
- ///
- /// 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);
+ 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 ();
}
+ }
- ///
- /// Scrolls the view area down a single line without changing the current selection.
- ///
- public void ScrollDown ()
- {
- if (ScrollOffsetVertical <= ContentHeight - 2) {
- ScrollOffsetVertical++;
- SetNeedsDisplay ();
- }
+ ///
+ /// Scrolls the view area up a single line without changing the current selection.
+ ///
+ public void ScrollUp ()
+ {
+ if (scrollOffsetVertical > 0) {
+ ScrollOffsetVertical--;
+ SetNeedsDisplay ();
}
+ }
- ///
- /// Scrolls the view area up a single line without changing the current selection.
- ///
- public void ScrollUp ()
- {
- if (scrollOffsetVertical > 0) {
- ScrollOffsetVertical--;
- SetNeedsDisplay ();
- }
- }
+ ///
+ /// Raises the event.
+ ///
+ ///
+ protected virtual void OnObjectActivated (ObjectActivatedEventArgs e) => ObjectActivated?.Invoke (this, e);
- ///
- /// 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) => HitTest (row)?.Model;
- ///
- /// 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)) {
- ///
- 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;
- }
-
- if (!HasFocus && CanFocus) {
- SetFocus ();
- }
-
- if (me.Flags == MouseFlags.WheeledDown) {
-
- ScrollDown ();
-
- return true;
- } else if (me.Flags == MouseFlags.WheeledUp) {
- ScrollUp ();
-
- return true;
- }
-
- if (me.Flags == MouseFlags.WheeledRight) {
-
- ScrollOffsetHorizontal++;
- SetNeedsDisplay ();
-
- return true;
- } else if (me.Flags == MouseFlags.WheeledLeft) {
- ScrollOffsetHorizontal--;
- SetNeedsDisplay ();
-
- return true;
- }
-
- if (me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
-
- // The line they clicked on a branch
- var clickedBranch = HitTest (me.Y);
-
- if (clickedBranch == null) {
- return false;
- }
-
- bool isExpandToggleAttempt = clickedBranch.IsHitOnExpandableSymbol (Driver, me.X);
-
- // If we are already selected (double click)
- if (Equals (SelectedObject, clickedBranch.Model)) {
- isExpandToggleAttempt = true;
- }
-
- // if they clicked on the +/- expansion symbol
- if (isExpandToggleAttempt) {
-
- if (clickedBranch.IsExpanded) {
- clickedBranch.Collapse ();
- InvalidateLineMap ();
- } else
- if (clickedBranch.CanExpand ()) {
- clickedBranch.Expand ();
- InvalidateLineMap ();
- } else {
- SelectedObject = clickedBranch.Model; // It is a leaf node
- multiSelectedRegions.Clear ();
- }
- } 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 ();
- }
-
- 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;
- }
-
- // 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));
-
- // mouse event is handled.
- return true;
- }
+ // do nothing
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;
-
- // click is outside any visible nodes
- if (idx < 0 || idx >= map.Count) {
- return null;
- }
-
- // The line they clicked on
- return map.ElementAt (idx);
+ if (!HasFocus && CanFocus) {
+ SetFocus ();
}
- ///
- /// Positions the cursor at the start of the selected objects line (if visible).
- ///
- public override void PositionCursor ()
- {
- if (CanFocus && HasFocus && Visible && SelectedObject != null) {
+ if (me.Flags == MouseFlags.WheeledDown) {
- var map = BuildLineMap ();
- var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
+ ScrollDown ();
- // if currently selected line is visible
- if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Bounds.Height) {
- Move (0, idx - ScrollOffsetVertical);
- } else {
- base.PositionCursor ();
- }
+ return true;
+ }
+ if (me.Flags == MouseFlags.WheeledUp) {
+ ScrollUp ();
- } else {
- base.PositionCursor ();
- }
+ return true;
}
- ///
- /// 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 (me.Flags == MouseFlags.WheeledRight) {
- if (ctrl) {
- CollapseAll (SelectedObject);
+ ScrollOffsetHorizontal++;
+ SetNeedsDisplay ();
+
+ return true;
+ }
+ if (me.Flags == MouseFlags.WheeledLeft) {
+ ScrollOffsetHorizontal--;
+ SetNeedsDisplay ();
+
+ return true;
+ }
+
+ if (me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+
+ // The line they clicked on a branch
+ var clickedBranch = HitTest (me.Y);
+
+ if (clickedBranch == null) {
+ return false;
+ }
+
+ var isExpandToggleAttempt = clickedBranch.IsHitOnExpandableSymbol (Driver, me.X);
+
+ // If we are already selected (double click)
+ if (Equals (SelectedObject, clickedBranch.Model)) {
+ isExpandToggleAttempt = true;
+ }
+
+ // if they clicked on the +/- expansion symbol
+ if (isExpandToggleAttempt) {
+
+ if (clickedBranch.IsExpanded) {
+ clickedBranch.Collapse ();
+ InvalidateLineMap ();
+ } else if (clickedBranch.CanExpand ()) {
+ clickedBranch.Expand ();
+ InvalidateLineMap ();
} else {
- Collapse (SelectedObject);
+ SelectedObject = clickedBranch.Model; // It is a leaf node
+ multiSelectedRegions.Clear ();
}
} else {
- var parent = GetParent (SelectedObject);
-
- 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 ();
-
- 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;
-
- 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;
- }
-
- 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) {
+ // 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 (SelectedObject == null) {
- SelectedObject = roots.Keys.FirstOrDefault ();
- } else {
- var map = BuildLineMap ();
-
- var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
-
- 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));
- }
- }
-
- SelectedObject = newBranch.Model;
-
- EnsureVisible (SelectedObject);
- }
- }
SetNeedsDisplay ();
+ return true;
}
- ///
- /// Moves the selection to the first child in the currently selected level.
- ///
- public void AdjustSelectionToBranchStart ()
- {
- var o = SelectedObject;
- if (o == null) {
- return;
+ // 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;
}
- var map = BuildLineMap ();
+ // Double click changes the selection to the clicked node as well as triggering
+ // activation otherwise it feels wierd
+ SelectedObject = clickedBranch.Model;
+ SetNeedsDisplay ();
- int currentIdx = map.IndexOf (b => Equals (b.Model, o));
+ // trigger activation event
+ OnObjectActivated (new ObjectActivatedEventArgs (this, clickedBranch.Model));
- if (currentIdx == -1) {
- return;
- }
+ // mouse event is handled.
+ return true;
+ }
+ return false;
+ }
- var currentBranch = map.ElementAt (currentIdx);
- var next = currentBranch;
+ ///
+ /// 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 ();
- for (; currentIdx >= 0; currentIdx--) {
- //if it is the beginning of the current depth of branch
- if (currentBranch.Depth != next.Depth) {
+ var idx = y + ScrollOffsetVertical;
- SelectedObject = currentBranch.Model;
- EnsureVisible (currentBranch.Model);
- SetNeedsDisplay ();
- return;
- }
-
- // look at next branch up for consideration
- currentBranch = next;
- next = map.ElementAt (currentIdx);
- }
-
- // We ran all the way to top of tree
- GoToFirst ();
+ // click is outside any visible nodes
+ if (idx < 0 || idx >= map.Count) {
+ return null;
}
- ///
- /// Moves the selection to the last child in the currently selected level.
- ///
- public void AdjustSelectionToBranchEnd ()
- {
- var o = SelectedObject;
- if (o == null) {
- return;
- }
+ // The line they clicked on
+ return map.ElementAt (idx);
+ }
+
+ ///
+ /// Positions the cursor at the start of the selected objects line (if visible).
+ ///
+ public override void PositionCursor ()
+ {
+ if (CanFocus && HasFocus && Visible && SelectedObject != null) {
var map = BuildLineMap ();
+ var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
- int currentIdx = map.IndexOf (b => Equals (b.Model, o));
-
- if (currentIdx == -1) {
- return;
+ // if currently selected line is visible
+ if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Bounds.Height) {
+ Move (0, idx - ScrollOffsetVertical);
+ } else {
+ base.PositionCursor ();
}
- var currentBranch = map.ElementAt (currentIdx);
- var next = currentBranch;
-
- for (; currentIdx < map.Count; currentIdx++) {
- //if it is the end of the current depth of branch
- if (currentBranch.Depth != next.Depth) {
-
- SelectedObject = currentBranch.Model;
- EnsureVisible (currentBranch.Model);
- SetNeedsDisplay ();
- return;
- }
-
- // look at next branch for consideration
- currentBranch = next;
- next = map.ElementAt (currentIdx);
- }
- GoToEnd ();
+ } else {
+ base.PositionCursor ();
}
+ }
- ///
- /// Sets the selection to the next branch that matches the .
- ///
- ///
- private void AdjustSelectionToNext (Func, bool> predicate)
- {
- var map = BuildLineMap ();
+ ///
+ /// 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)) {
- // empty map means we can't select anything anyway
- if (map.Count == 0) {
- return;
+ if (ctrl) {
+ CollapseAll (SelectedObject);
+ } else {
+ Collapse (SelectedObject);
}
+ } else {
+ var parent = GetParent (SelectedObject);
- // 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));
- }
-
- // 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;
- }
+ if (parent != null) {
+ SelectedObject = parent;
+ AdjustSelection (0);
+ SetNeedsDisplay ();
}
}
+ }
- ///
- /// Adjusts the to ensure the given
- /// is visible. Has no effect if already visible.
- ///
- public void EnsureVisible (T model)
- {
+ ///
+ /// Changes the to the first root object and resets
+ /// the to 0.
+ ///
+ public void GoToFirst ()
+ {
+ ScrollOffsetVertical = 0;
+ SelectedObject = roots.Keys.FirstOrDefault ();
+
+ 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;
+
+ 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;
+ }
+
+ 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 ();
+ }
+
+ if (SelectedObject == null) {
+ SelectedObject = roots.Keys.FirstOrDefault ();
+ } else {
var map = BuildLineMap ();
- var idx = map.IndexOf (b => Equals (b.Model, model));
+ var idx = map.IndexOf (b => b.Model.Equals (SelectedObject));
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;
-
- 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));
- }
- }
-
- ///
- /// 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;
- }
-
- 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;
- }
-
- 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 ();
- }
-
- 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 ();
- }
-
- ///
- /// Implementation of and . Performs
- /// operation and updates selection if disapeared.
- ///
- ///
- ///
- protected void CollapseImpl (T toCollapse, bool all)
- {
- if (toCollapse == null) {
- return;
- }
-
- var branch = ObjectToBranch (toCollapse);
-
- // Nothing to collapse
- if (branch == null) {
- return;
- }
-
- if (all) {
- branch.CollapseAll ();
+ // The current selection has disapeared!
+ SelectedObject = roots.Keys.FirstOrDefault ();
} else {
- branch.Collapse ();
- }
+ var newIdx = Math.Min (Math.Max (0, idx + offset), map.Count - 1);
- if (SelectedObject != null && ObjectToBranch (SelectedObject) == null) {
- // If the old selection suddenly became invalid then clear it
- SelectedObject = null;
- }
+ var newBranch = map.ElementAt (newIdx);
- InvalidateLineMap ();
- SetNeedsDisplay ();
- }
-
- ///
- /// 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 ();
-
- // 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;
+ // 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;
+
+ EnsureVisible (SelectedObject);
}
}
+ SetNeedsDisplay ();
+ }
- ///
- /// Selects all objects in the tree when is enabled otherwise
- /// does nothing.
- ///
- public void SelectAll ()
- {
- if (!MultiSelect) {
+ ///
+ /// 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 ();
+
+ var currentIdx = map.IndexOf (b => Equals (b.Model, o));
+
+ if (currentIdx == -1) {
+ return;
+ }
+
+ var currentBranch = map.ElementAt (currentIdx);
+ var next = currentBranch;
+
+ for (; currentIdx >= 0; currentIdx--) {
+ //if it is the beginning of the current depth of branch
+ if (currentBranch.Depth != next.Depth) {
+
+ SelectedObject = currentBranch.Model;
+ EnsureVisible (currentBranch.Model);
+ SetNeedsDisplay ();
return;
}
- multiSelectedRegions.Clear ();
+ // look at next branch up for consideration
+ currentBranch = next;
+ next = map.ElementAt (currentIdx);
+ }
- var map = BuildLineMap ();
+ // We ran all the way to top of tree
+ GoToFirst ();
+ }
- if (map.Count == 0) {
+ ///
+ /// Moves the selection to the last child in the currently selected level.
+ ///
+ public void AdjustSelectionToBranchEnd ()
+ {
+ var o = SelectedObject;
+ if (o == null) {
+ return;
+ }
+
+ var map = BuildLineMap ();
+
+ var currentIdx = map.IndexOf (b => Equals (b.Model, o));
+
+ if (currentIdx == -1) {
+ return;
+ }
+
+ var currentBranch = map.ElementAt (currentIdx);
+ var next = currentBranch;
+
+ for (; currentIdx < map.Count; currentIdx++) {
+ //if it is the end of the current depth of branch
+ if (currentBranch.Depth != next.Depth) {
+
+ SelectedObject = currentBranch.Model;
+ EnsureVisible (currentBranch.Model);
+ SetNeedsDisplay ();
return;
}
- multiSelectedRegions.Push (new TreeSelection (map.ElementAt (0), map.Count, map));
- SetNeedsDisplay ();
+ // look at next branch for consideration
+ currentBranch = next;
+ next = map.ElementAt (currentIdx);
+ }
+ GoToEnd ();
+ }
- OnSelectionChanged (new SelectionChangedEventArgs (this, SelectedObject, SelectedObject));
+ ///
+ /// 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;
}
- ///
- /// Raises the SelectionChanged event.
- ///
- ///
- protected virtual void OnSelectionChanged (SelectionChangedEventArgs e)
- {
- SelectionChanged?.Invoke (this, e);
+ // 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));
}
- ///
- /// Raises the DrawLine event
- ///
- ///
- internal void OnDrawLine (DrawTreeViewLineEventArgs e)
- {
- DrawLine?.Invoke (this, e);
+ // if currently selected object mysteriously vanished, search from beginning
+ if (idxStart == -1) {
+ idxStart = 0;
}
- ///
- protected override void Dispose (bool disposing)
- {
- base.Dispose (disposing);
-
- ColorGetter = null;
+ // 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;
+ }
}
}
- class TreeSelection where T : class {
- public Branch Origin { get; }
+ ///
+ /// Adjusts the to ensure the given
+ /// is visible. Has no effect if already visible.
+ ///
+ public void EnsureVisible (T model)
+ {
+ var map = BuildLineMap ();
- private HashSet included = new HashSet ();
-
- ///
- /// 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);
- }
+ var idx = map.IndexOf (b => Equals (b.Model, model));
+ if (idx == -1) {
+ return;
}
- public bool Contains (T model)
- {
- return included.Contains (model);
+
+ /*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 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 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 ();
+ }
+
+ ///
+ /// 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 ();
+ }
+
+ ///
+ /// 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) => ObjectToBranch (o)?.CanExpand () ?? false;
+
+ ///
+ /// Returns true if the given object is exposed in the tree and
+ /// expanded otherwise false.
+ ///
+ ///
+ ///
+ public bool IsExpanded (T o) => 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 ();
+ }
+
+ ///
+ /// Implementation of and . Performs
+ /// operation and updates selection if disapeared.
+ ///
+ ///
+ ///
+ protected void CollapseImpl (T toCollapse, bool all)
+ {
+ if (toCollapse == null) {
+ return;
+ }
+
+ var branch = ObjectToBranch (toCollapse);
+
+ // Nothing to collapse
+ if (branch == null) {
+ return;
+ }
+
+ if (all) {
+ branch.CollapseAll ();
+ } else {
+ branch.Collapse ();
+ }
+
+ if (SelectedObject != null && ObjectToBranch (SelectedObject) == null) {
+ // If the old selection suddenly became invalid then clear it
+ SelectedObject = null;
+ }
+
+ InvalidateLineMap ();
+ SetNeedsDisplay ();
+ }
+
+ ///
+ /// 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.
+ ///
+ Branch ObjectToBranch (T toFind) => BuildLineMap ().FirstOrDefault (o => o.Model.Equals (toFind));
+
+ ///
+ /// 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));
+
+ ///
+ /// 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;
+ }
+ }
+ }
+
+ ///
+ /// Selects all objects in the tree when is enabled otherwise
+ /// does nothing.
+ ///
+ public void SelectAll ()
+ {
+ if (!MultiSelect) {
+ return;
+ }
+
+ multiSelectedRegions.Clear ();
+
+ 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));
+ }
+
+ ///
+ /// Raises the SelectionChanged event.
+ ///
+ ///
+ protected virtual void OnSelectionChanged (SelectionChangedEventArgs e) => SelectionChanged?.Invoke (this, e);
+
+ ///
+ /// Raises the DrawLine event
+ ///
+ ///
+ internal void OnDrawLine (DrawTreeViewLineEventArgs e) => DrawLine?.Invoke (this, e);
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ base.Dispose (disposing);
+
+ ColorGetter = null;
+ }
+}
+
+class TreeSelection where T : class {
+
+ 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 Branch Origin { get; }
+
+ public bool Contains (T model) => included.Contains (model);
}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/Window.cs b/Terminal.Gui/Views/Window.cs
index 8d4c3a2cd..5e1e4b87c 100644
--- a/Terminal.Gui/Views/Window.cs
+++ b/Terminal.Gui/Views/Window.cs
@@ -1,60 +1,69 @@
-using System;
-using System.Collections;
+using System.Linq;
using System.Text.Json.Serialization;
-using System.Text;
-using Terminal.Gui;
-using static Terminal.Gui.ConfigurationManager;
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+///
+/// A with set to
+/// . Provides a container for other views.
+///
+///
+///
+/// If any subview is a button and the property is set to true, the Enter key
+/// will invoke the command on that subview.
+///
+///
+public class Window : Toplevel {
+ ///
+ /// Initializes a new instance of the class using
+ /// positioning.
+ ///
+ public Window () => SetInitialProperties ();
///
- /// A with set to .
+ /// Initializes a new instance of the class using
+ /// positioning.
+ ///
+ public Window (Rect frame) : base (frame) => SetInitialProperties ();
+
+ // TODO: enable this
+ /////
+ ///// The default for 's border. The default is .
+ /////
+ /////
+ ///// This property can be set in a Theme to change the default for all s.
+ /////
+ /////[SerializableConfigurationProperty (Scope = typeof (ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
+ ////public static ColorScheme DefaultColorScheme { get; set; } = Colors.ColorSchemes ["Base"];
+
+ ///
+ /// The default for 's border. The default is
+ /// .
///
///
- ///
- /// This is a helper class to simplify creating a with a border.
- ///
+ /// This property can be set in a Theme to change the default for all
+ /// s.
///
- public class Window : Toplevel {
- ///
- /// Initializes a new instance of the class using positioning.
- ///
- public Window () : base () {
- SetInitialProperties ();
- }
+ [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+ [JsonConverter (typeof (JsonStringEnumConverter))]
+ public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
- ///
- /// Initializes a new instance of the class using positioning.
- ///
- public Window (Rect frame) : base (frame)
- {
- SetInitialProperties ();
- }
+ void SetInitialProperties ()
+ {
+ CanFocus = true;
+ ColorScheme = Colors.ColorSchemes ["Base"]; // TODO: make this a theme property
+ BorderStyle = DefaultBorderStyle;
- // TODO: enable this
- /////
- ///// The default for 's border. The default is .
- /////
- /////
- ///// This property can be set in a Theme to change the default for all s.
- /////
- /////[SerializableConfigurationProperty (Scope = typeof (ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
- ////public static ColorScheme DefaultColorScheme { get; set; } = Colors.Base;
+ // This enables the default button to be activated by the Enter key.
+ AddCommand (Command.Accept, () => {
+ // TODO: Perhaps all views should support the concept of being default?
+ if (Subviews.FirstOrDefault (v => v is Button { IsDefault: true, Enabled: true }) is Button defaultBtn) {
+ defaultBtn.InvokeCommand (Command.Accept);
+ return true;
+ }
+ return false;
+ });
- ///
- /// The default for 's border. The default is .
- ///
- ///
- /// This property can be set in a Theme to change the default for all s.
- ///
- [SerializableConfigurationProperty (Scope = typeof (ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
- public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
-
- void SetInitialProperties ()
- {
- CanFocus = true;
- ColorScheme = Colors.Base; // TODO: make this a theme property
- BorderStyle = DefaultBorderStyle;
- }
+ KeyBindings.Add (Key.Enter, Command.Accept);
}
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs
index 722a585c3..85e00166e 100644
--- a/Terminal.Gui/Views/Wizard/Wizard.cs
+++ b/Terminal.Gui/Views/Wizard/Wizard.cs
@@ -512,13 +512,13 @@ public class Wizard : Dialog {
SizeStep (step);
}
if (base.Modal) {
- ColorScheme = Colors.Dialog;
+ ColorScheme = Colors.ColorSchemes ["Dialog"];
BorderStyle = LineStyle.Rounded;
} else {
if (SuperView != null) {
ColorScheme = SuperView.ColorScheme;
} else {
- ColorScheme = Colors.Base;
+ ColorScheme = Colors.ColorSchemes ["Base"];
}
CanFocus = true;
BorderStyle = LineStyle.None;
diff --git a/Terminal.sln b/Terminal.sln
index fcc42be94..8f06e5fd6 100644
--- a/Terminal.sln
+++ b/Terminal.sln
@@ -26,7 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
nuget.config = nuget.config
.github\workflows\publish.yml = .github\workflows\publish.yml
README.md = README.md
- Terminal.Gui\.vscode\settings.json = Terminal.Gui\.vscode\settings.json
Terminal.sln.DotSettings = Terminal.sln.DotSettings
testenvironments.json = testenvironments.json
EndProjectSection
diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json
index c105de4e1..041a5c22d 100644
--- a/UICatalog/Properties/launchSettings.json
+++ b/UICatalog/Properties/launchSettings.json
@@ -69,6 +69,10 @@
"ListView & ComboBox": {
"commandName": "Project",
"commandLineArgs": "\"ListView & ComboBox\""
+ },
+ "Frames Demo": {
+ "commandName": "Project",
+ "commandLineArgs": "\"Frames Demo\""
}
}
}
\ No newline at end of file
diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs
index 43b171a58..16910498f 100644
--- a/UICatalog/Scenario.cs
+++ b/UICatalog/Scenario.cs
@@ -1,271 +1,296 @@
-using System.Text;
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Terminal.Gui;
-namespace UICatalog {
+namespace UICatalog;
+
+///
+/// Base class for each demo/scenario.
+///
+/// To define a new scenario:
+///
+/// -
+///
+/// Create a new .cs file in the Scenarios directory that derives from
+/// .
+///
+///
+/// -
+///
+/// Annotate the derived class with a
+/// attribute specifying the scenario's name and
+/// description.
+///
+///
+/// -
+///
+/// Add one or more attributes to the class specifying
+/// which categories the scenario belongs to. If you don't specify a category the scenario will
+/// show up in "_All".
+///
+///
+/// -
+///
+/// Implement the override which will be called when a user
+/// selects the scenario to run.
+///
+///
+/// -
+///
+/// Optionally, implement the and/or overrides
+/// to provide a custom implementation.
+///
+///
+///
+///
+///
+/// The UI Catalog program uses reflection to find all scenarios and adds them to the
+/// ListViews. Press ENTER to run the selected scenario. Press the default quit key to quit. /
+///
+///
+///
+/// The example below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
+///
+/// using Terminal.Gui;
+///
+/// namespace UICatalog {
+/// [ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
+/// [ScenarioCategory ("Controls")]
+/// class MyScenario : Scenario {
+/// public override void Setup ()
+/// {
+/// // Put your scenario code here, e.g.
+/// Win.Add (new Button ("Press me!") {
+/// X = Pos.Center (),
+/// Y = Pos.Center (),
+/// Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
+/// });
+/// }
+/// }
+/// }
+///
+///
+public class Scenario : IDisposable {
+
+ static int _maxScenarioNameLen = 30;
+ bool _disposedValue;
+
+ public string Theme = "Default";
+ public string TopLevelColorScheme = "Base";
+
///
- /// Base class for each demo/scenario.
- ///
- /// To define a new scenario:
- ///
- /// - Create a new .cs file in the Scenarios directory that derives from .
- /// - Annotate the derived class with a attribute specifying the scenario's name and description.
- /// - Add one or more attributes to the class specifying which categories the scenario belongs to. If you don't specify a category the scenario will show up in "_All".
- /// - Implement the override which will be called when a user selects the scenario to run.
- /// - Optionally, implement the and/or overrides to provide a custom implementation.
- ///
- ///
- ///
- /// The UI Catalog program uses reflection to find all scenarios and adds them to the
- /// ListViews. Press ENTER to run the selected scenario. Press the default quit key to quit. /
- ///
+ /// The Window for the . This should be set to in most
+ /// cases.
///
- ///
- /// The example below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
- ///
- /// using Terminal.Gui;
- ///
- /// namespace UICatalog {
- /// [ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
- /// [ScenarioCategory ("Controls")]
- /// class MyScenario : Scenario {
- /// public override void Setup ()
- /// {
- /// // Put your scenario code here, e.g.
- /// Win.Add (new Button ("Press me!") {
- /// X = Pos.Center (),
- /// Y = Pos.Center (),
- /// Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
- /// });
- /// }
- /// }
- /// }
- ///
- ///
- public class Scenario : IDisposable {
- private bool _disposedValue;
+ public Window Win { get; set; }
- public string Theme = "Default";
- public string TopLevelColorScheme = "Base";
+ public void Dispose ()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose (disposing: true);
+ GC.SuppressFinalize (this);
+ }
- ///
- /// The Window for the . This should be set to in most cases.
- ///
- public Window Win { get; set; }
+ ///
+ /// Helper that provides the default implementation with a frame and
+ /// label showing the name of the and logic to exit back to
+ /// the Scenario picker UI.
+ /// Override to provide any behavior needed.
+ ///
+ ///
+ ///
+ /// The base implementation calls and creates a for
+ ///
+ /// and adds it to .
+ ///
+ ///
+ /// Overrides that do not call the base., must call
+ /// before creating any views or calling other Terminal.Gui APIs.
+ ///
+ ///
+ public virtual void Init ()
+ {
+ Application.Init ();
- ///
- /// Helper that provides the default implementation with a frame and
- /// label showing the name of the and logic to exit back to
- /// the Scenario picker UI.
- /// Override to provide any behavior needed.
- ///
- ///
- ///
- /// The base implementation calls and creates a for
- /// and adds it to .
- ///
- ///
- /// Overrides that do not call the base., must call
- /// before creating any views or calling other Terminal.Gui APIs.
- ///
- ///
- public virtual void Init ()
- {
- Application.Init ();
-
- ConfigurationManager.Themes.Theme = Theme;
- ConfigurationManager.Apply ();
+ ConfigurationManager.Themes.Theme = Theme;
+ ConfigurationManager.Apply ();
- Win = new Window () {
- Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
- X = 0,
- Y = 0,
- Width = Dim.Fill (),
- Height = Dim.Fill (),
- ColorScheme = Colors.ColorSchemes [TopLevelColorScheme],
- };
- Application.Top.Add (Win);
+ Win = new Window {
+ Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+ X = 0,
+ Y = 0,
+ Width = Dim.Fill (),
+ Height = Dim.Fill (),
+ ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]
+ };
+ Application.Top.Add (Win);
+ }
+
+ ///
+ /// Helper to get the Name (defined in )
+ ///
+ ///
+ public string GetName () => ScenarioMetadata.GetName (GetType ());
+
+ ///
+ /// Helper to get the Description (defined in )
+ ///
+ ///
+ public string GetDescription () => ScenarioMetadata.GetDescription (GetType ());
+
+ ///
+ /// Helper function to get the list of categories a belongs to (defined in
+ /// )
+ ///
+ /// list of category names
+ public List GetCategories () => ScenarioCategory.GetCategories (GetType ());
+
+ ///
+ /// Gets the Scenario Name + Description with the Description padded
+ /// based on the longest known Scenario name.
+ ///
+ ///
+ public override string ToString () => $"{GetName ().PadRight (_maxScenarioNameLen)}{GetDescription ()}";
+
+ ///
+ /// Override this to implement the setup logic (create controls, etc...).
+ ///
+ /// This is typically the best place to put scenario logic code.
+ public virtual void Setup () { }
+
+ ///
+ /// Runs the . Override to start the
+ /// using a different than `Top`.
+ ///
+ ///
+ /// Overrides that do not call the base., must call before returning.
+ ///
+ public virtual void Run () =>
+ // Must explicit call Application.Shutdown method to shutdown.
+ Application.Run (Application.Top);
+
+ ///
+ /// Stops the scenario. Override to change shutdown behavior for the .
+ ///
+ public virtual void RequestStop () => Application.RequestStop ();
+
+ ///
+ /// Returns a list of all Categories set by all of the s defined in the project.
+ ///
+ internal static List GetAllCategories ()
+ {
+ var categories = new List ();
+ foreach (var type in typeof (Scenario).Assembly.GetTypes ()
+ .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
+ var attrs = System.Attribute.GetCustomAttributes (type).ToList ();
+ categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList ();
}
- ///
- /// Defines the metadata (Name and Description) for a
- ///
- [System.AttributeUsage (System.AttributeTargets.Class)]
- public class ScenarioMetadata : System.Attribute {
- ///
- /// Name
- ///
- public string Name { get; set; }
+ // Sort
+ categories = categories.OrderBy (c => c).ToList ();
- ///
- /// Description
- ///
- public string Description { get; set; }
+ // Put "All" at the top
+ categories.Insert (0, "All Scenarios");
+ return categories;
+ }
- public ScenarioMetadata (string Name, string Description)
- {
- this.Name = Name;
- this.Description = Description;
+ ///
+ /// Returns a list of all instanaces defined in the project, sorted by
+ /// .
+ /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
+ ///
+ public static List GetScenarios ()
+ {
+ var objects = new List ();
+ foreach (var type in typeof (Scenario).Assembly.ExportedTypes
+ .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
+ var scenario = (Scenario)Activator.CreateInstance (type);
+ objects.Add (scenario);
+ _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
+ }
+ return objects.OrderBy (s => s.GetName ()).ToList ();
+ }
+
+ protected virtual void Dispose (bool disposing)
+ {
+ if (!_disposedValue) {
+ if (disposing) {
+ // TODO: dispose managed state (managed objects)
}
- ///
- /// Static helper function to get the Name given a Type
- ///
- ///
- ///
- public static string GetName (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Name;
-
- ///
- /// Static helper function to get the Description given a Type
- ///
- ///
- ///
- public static string GetDescription (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Description;
- }
-
- ///
- /// Helper to get the Name (defined in )
- ///
- ///
- public string GetName () => ScenarioMetadata.GetName (this.GetType ());
-
- ///
- /// Helper to get the Description (defined in )
- ///
- ///
- public string GetDescription () => ScenarioMetadata.GetDescription (this.GetType ());
-
- ///
- /// Defines the category names used to catagorize a
- ///
- [System.AttributeUsage (System.AttributeTargets.Class, AllowMultiple = true)]
- public class ScenarioCategory : System.Attribute {
- ///
- /// Category Name
- ///
- public string Name { get; set; }
-
- public ScenarioCategory (string Name) => this.Name = Name;
-
- ///
- /// Static helper function to get the Name given a Type
- ///
- ///
- /// Name of the category
- public static string GetName (Type t) => ((ScenarioCategory)System.Attribute.GetCustomAttributes (t) [0]).Name;
-
- ///
- /// Static helper function to get the Categories given a Type
- ///
- ///
- /// list of category names
- public static List GetCategories (Type t) => System.Attribute.GetCustomAttributes (t)
- .ToList ()
- .Where (a => a is ScenarioCategory)
- .Select (a => ((ScenarioCategory)a).Name)
- .ToList ();
- }
-
- ///
- /// Helper function to get the list of categories a belongs to (defined in )
- ///
- /// list of category names
- public List GetCategories () => ScenarioCategory.GetCategories (this.GetType ());
-
- private static int _maxScenarioNameLen = 30;
-
- ///
- /// Gets the Scenario Name + Description with the Description padded
- /// based on the longest known Scenario name.
- ///
- ///
- public override string ToString () => $"{GetName ().PadRight(_maxScenarioNameLen)}{GetDescription ()}";
-
- ///
- /// Override this to implement the setup logic (create controls, etc...).
- ///
- /// This is typically the best place to put scenario logic code.
- public virtual void Setup ()
- {
- }
-
- ///
- /// Runs the . Override to start the
- /// using a different than `Top`.
- ///
- ///
- /// Overrides that do not call the base., must call before returning.
- ///
- public virtual void Run ()
- {
- // Must explicit call Application.Shutdown method to shutdown.
- Application.Run (Application.Top);
- }
-
- ///
- /// Stops the scenario. Override to change shutdown behavior for the .
- ///
- public virtual void RequestStop ()
- {
- Application.RequestStop ();
- }
-
- ///
- /// Returns a list of all Categories set by all of the s defined in the project.
- ///
- internal static List GetAllCategories ()
- {
- List categories = new List ();
- foreach (Type type in typeof (Scenario).Assembly.GetTypes ()
- .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
- List attrs = System.Attribute.GetCustomAttributes (type).ToList ();
- categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList ();
- }
-
- // Sort
- categories = categories.OrderBy (c => c).ToList ();
-
- // Put "All" at the top
- categories.Insert (0, "All Scenarios");
- return categories;
- }
-
- ///
- /// Returns a list of all instanaces defined in the project, sorted by .
- /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
- ///
- public static List GetScenarios ()
- {
- List objects = new List ();
- foreach (Type type in typeof (Scenario).Assembly.ExportedTypes
- .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
- var scenario = (Scenario)Activator.CreateInstance (type);
- objects.Add (scenario);
- _maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
- }
- return objects.OrderBy (s => s.GetName ()).ToList ();
- }
-
- protected virtual void Dispose (bool disposing)
- {
- if (!_disposedValue) {
- if (disposing) {
- // TODO: dispose managed state (managed objects)
- }
-
- // TODO: free unmanaged resources (unmanaged objects) and override finalizer
- // TODO: set large fields to null
- _disposedValue = true;
- }
- }
-
- public void Dispose ()
- {
- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
- Dispose (disposing: true);
- GC.SuppressFinalize (this);
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ _disposedValue = true;
}
}
-}
+
+ ///
+ /// Defines the metadata (Name and Description) for a
+ ///
+ [AttributeUsage (AttributeTargets.Class)]
+ public class ScenarioMetadata : System.Attribute {
+
+ public ScenarioMetadata (string Name, string Description)
+ {
+ this.Name = Name;
+ this.Description = Description;
+ }
+
+ ///
+ /// Name
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Description
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Static helper function to get the Name given a Type
+ ///
+ ///
+ ///
+ public static string GetName (Type t) => ((ScenarioMetadata)GetCustomAttributes (t) [0]).Name;
+
+ ///
+ /// Static helper function to get the Description given a Type
+ ///
+ ///
+ ///
+ public static string GetDescription (Type t) => ((ScenarioMetadata)GetCustomAttributes (t) [0]).Description;
+ }
+
+ ///
+ /// Defines the category names used to catagorize a
+ ///
+ [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)]
+ public class ScenarioCategory : System.Attribute {
+
+ public ScenarioCategory (string Name) => this.Name = Name;
+
+ ///
+ /// Category Name
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Static helper function to get the Name given a Type
+ ///
+ ///
+ /// Name of the category
+ public static string GetName (Type t) => ((ScenarioCategory)GetCustomAttributes (t) [0]).Name;
+
+ ///
+ /// Static helper function to get the Categories given a Type
+ ///
+ ///
+ /// list of category names
+ public static List GetCategories (Type t) => GetCustomAttributes (t)
+ .ToList ()
+ .Where (a => a is ScenarioCategory)
+ .Select (a => ((ScenarioCategory)a).Name)
+ .ToList ();
+ }
+}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/Adornments.cs b/UICatalog/Scenarios/Adornments.cs
new file mode 100644
index 000000000..f6424932c
--- /dev/null
+++ b/UICatalog/Scenarios/Adornments.cs
@@ -0,0 +1,441 @@
+using System;
+using System.Linq;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Adornments Demo", "Demonstrates Margin, Border, and Padding on Views.")]
+[ScenarioCategory ("Layout")]
+[ScenarioCategory ("Borders")]
+public class Adornments : Scenario {
+
+ public override void Init ()
+ {
+ Application.Init ();
+ ConfigurationManager.Themes.Theme = Theme;
+ ConfigurationManager.Apply ();
+ Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
+
+ var view = new Window { Title = "The Window" };
+ var tf1 = new TextField ("TextField") { Width = 10 };
+ var color = new ColorPicker () { Title = "BG", BoxHeight = 1, BoxWidth =1, X = Pos.AnchorEnd(11) };
+ color.BorderStyle = LineStyle.RoundedDotted;
+ color.ColorChanged += (s, e) => {
+ color.SuperView.ColorScheme = new ColorScheme (color.SuperView.ColorScheme) {
+ Normal = new Attribute(color.SuperView.ColorScheme.Normal.Foreground, e.Color)
+ };
+ };
+
+ var button = new Button ("Press me!") {
+ X = Pos.Center (),
+ Y = Pos.Center ()
+ };
+ button.Clicked += (s, e) => MessageBox.Query (20, 7, "Hi", $"Am I a {view.GetType ().Name}?", "Yes", "No");
+
+ var label = new TextView () {
+ X = Pos.Center (),
+ Y = Pos.Bottom (button),
+ Title = "Title",
+ Text = "I have a 3 row top border.\nMy border inherits from the SuperView.",
+ Width = 40,
+ Height = 6 // TODO: Use Dim.Auto
+ };
+ label.Border.Thickness = new Thickness (1, 3, 1, 1);
+
+ var tf2 = new Button ("Button") {
+ X = Pos.AnchorEnd (10),
+ Y = Pos.AnchorEnd (1),
+ Width = 10
+ };
+ var tv = new Label {
+ Y = Pos.AnchorEnd (3),
+ Width = 25,
+ Height = Dim.Fill (),
+ Text = "Label\nY=AnchorEnd(3),Height=Dim.Fill()"
+ };
+
+ view.Margin.Data = "Margin";
+ view.Margin.Thickness = new Thickness (3);
+
+ view.Border.Data = "Border";
+ view.Border.Thickness = new Thickness (3);
+
+ view.Padding.Data = "Padding";
+ view.Padding.Thickness = new Thickness (3);
+
+ view.Add (tf1, color, button, label, tf2, tv);
+
+ var editor = new AdornmentsEditor {
+ Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+ ColorScheme = Colors.ColorSchemes [TopLevelColorScheme],
+ };
+ view.X = 36;
+ view.Y = 0;
+ view.Width = Dim.Fill ();
+ view.Height = Dim.Fill ();
+
+ editor.Initialized += (s, e) => {
+ editor.ViewToEdit = view;
+ };
+ //view.Margin.ColorScheme = new ColorScheme (Colors.ColorSchemes ["Dialog"]);
+ //view.Border.ColorScheme = new ColorScheme (Colors.ColorSchemes ["Error"]);
+ //view.Padding.ColorScheme = new ColorScheme (Colors.ColorSchemes ["Menu"]);
+
+ Application.Run (editor);
+ Application.Shutdown ();
+ }
+
+ public override void Run () { }
+
+ public class AdornmentEditor : View {
+ readonly ColorPicker _backgroundColorPicker = new () {
+ Title = "BG",
+ BoxWidth = 1,
+ BoxHeight = 1,
+ BorderStyle = LineStyle.Single,
+ SuperViewRendersLineCanvas = true
+ };
+
+ readonly ColorPicker _foregroundColorPicker = new () {
+ Title = "FG",
+ BoxWidth = 1,
+ BoxHeight = 1,
+ BorderStyle = LineStyle.Single,
+ SuperViewRendersLineCanvas = true
+ };
+
+ TextField _bottomEdit;
+ bool _isUpdating;
+ TextField _leftEdit;
+ TextField _rightEdit;
+ Thickness _thickness;
+ TextField _topEdit;
+
+ public AdornmentEditor ()
+ {
+ Margin.Thickness = new Thickness (0);
+ BorderStyle = LineStyle.Double;
+ Initialized += AdornmentEditor_Initialized;
+ }
+
+ public Attribute Color {
+ get => new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor);
+ set {
+ _foregroundColorPicker.SelectedColor = value.Foreground.ColorName;
+ _backgroundColorPicker.SelectedColor = value.Background.ColorName;
+ }
+ }
+
+ public Thickness Thickness {
+ get => _thickness;
+ set {
+ if (_isUpdating) {
+ return;
+ }
+ _thickness = value;
+ ThicknessChanged?.Invoke (this, new ThicknessEventArgs { Thickness = Thickness });
+ if (IsInitialized) {
+ _isUpdating = true;
+ if (_topEdit.Text != _thickness.Top.ToString ()) {
+ _topEdit.Text = _thickness.Top.ToString ();
+ }
+ if (_leftEdit.Text != _thickness.Left.ToString ()) {
+ _leftEdit.Text = _thickness.Left.ToString ();
+ }
+ if (_rightEdit.Text != _thickness.Right.ToString ()) {
+ _rightEdit.Text = _thickness.Right.ToString ();
+ }
+ if (_bottomEdit.Text != _thickness.Bottom.ToString ()) {
+ _bottomEdit.Text = _thickness.Bottom.ToString ();
+ }
+ _isUpdating = false;
+ }
+ }
+ }
+
+ public event EventHandler ThicknessChanged;
+
+ public event EventHandler AttributeChanged;
+
+ void AdornmentEditor_Initialized (object sender, EventArgs e)
+ {
+ var editWidth = 3;
+
+ _topEdit = new TextField ("") {
+ X = Pos.Center (),
+ Y = 0,
+ Width = editWidth
+ };
+ _topEdit.TextChanging += Edit_TextChanging;
+ Add (_topEdit);
+
+ _leftEdit = new TextField ("") {
+ X = Pos.Left (_topEdit) - editWidth,
+ Y = Pos.Bottom (_topEdit),
+ Width = editWidth
+ };
+ _leftEdit.TextChanging += Edit_TextChanging;
+ Add (_leftEdit);
+
+ _rightEdit = new TextField ("") {
+ X = Pos.Right (_topEdit),
+ Y = Pos.Bottom (_topEdit),
+ Width = editWidth
+ };
+ _rightEdit.TextChanging += Edit_TextChanging;
+ Add (_rightEdit);
+
+ _bottomEdit = new TextField ("") {
+ X = Pos.Center (),
+ Y = Pos.Bottom (_leftEdit),
+ Width = editWidth
+ };
+ _bottomEdit.TextChanging += Edit_TextChanging;
+ Add (_bottomEdit);
+
+ var copyTop = new Button ("Copy Top") {
+ X = Pos.Center () + 1,
+ Y = Pos.Bottom (_bottomEdit)
+ };
+ copyTop.Clicked += (s, e) => {
+ Thickness = new Thickness (Thickness.Top);
+ if (string.IsNullOrEmpty (_topEdit.Text)) {
+ _topEdit.Text = "0";
+ }
+ _bottomEdit.Text = _leftEdit.Text = _rightEdit.Text = _topEdit.Text;
+ };
+ Add (copyTop);
+
+ // Foreground ColorPicker.
+ _foregroundColorPicker.X = -1;
+ _foregroundColorPicker.Y = Pos.Bottom (copyTop) + 1;
+ _foregroundColorPicker.SelectedColor = Color.Foreground.ColorName;
+ _foregroundColorPicker.ColorChanged += (o, a) =>
+ AttributeChanged?.Invoke (this,
+ new Attribute (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor));
+ Add (_foregroundColorPicker);
+
+ // Background ColorPicker.
+ _backgroundColorPicker.X = Pos.Right (_foregroundColorPicker) - 1;
+ _backgroundColorPicker.Y = Pos.Top (_foregroundColorPicker);
+ _backgroundColorPicker.SelectedColor = Color.Background.ColorName;
+ _backgroundColorPicker.ColorChanged += (o, a) =>
+ AttributeChanged?.Invoke (this,
+ new Attribute (
+ _foregroundColorPicker.SelectedColor,
+ _backgroundColorPicker.SelectedColor));
+ Add (_backgroundColorPicker);
+
+ _topEdit.Text = $"{Thickness.Top}";
+ _leftEdit.Text = $"{Thickness.Left}";
+ _rightEdit.Text = $"{Thickness.Right}";
+ _bottomEdit.Text = $"{Thickness.Bottom}";
+
+ LayoutSubviews ();
+ Height = GetAdornmentsThickness ().Vertical + 4 + 4;
+ Width = GetAdornmentsThickness ().Horizontal + _foregroundColorPicker.Frame.Width * 2 - 3;
+ }
+
+ void Edit_TextChanging (object sender, TextChangingEventArgs e)
+ {
+ try {
+ if (string.IsNullOrEmpty (e.NewText)) {
+ e.Cancel = true;
+ ((TextField)sender).Text = "0";
+ return;
+ }
+ switch (sender.ToString ()) {
+ case var s when s == _topEdit.ToString ():
+ Thickness = new Thickness (Thickness.Left,
+ int.Parse (e.NewText), Thickness.Right,
+ Thickness.Bottom);
+ break;
+ case var s when s == _leftEdit.ToString ():
+ Thickness = new Thickness (int.Parse (e.NewText),
+ Thickness.Top, Thickness.Right,
+ Thickness.Bottom);
+ break;
+ case var s when s == _rightEdit.ToString ():
+ Thickness = new Thickness (Thickness.Left,
+ Thickness.Top, int.Parse (e.NewText),
+ Thickness.Bottom);
+ break;
+ case var s when s == _bottomEdit.ToString ():
+ Thickness = new Thickness (Thickness.Left,
+ Thickness.Top, Thickness.Right,
+ int.Parse (e.NewText));
+ break;
+ }
+ } catch {
+ if (!string.IsNullOrEmpty (e.NewText)) {
+ e.Cancel = true;
+ }
+ }
+ }
+ }
+
+ public class AdornmentsEditor : Window {
+ AdornmentEditor _borderEditor;
+ CheckBox _diagCheckBox;
+ AdornmentEditor _marginEditor;
+ String _origTitle = string.Empty;
+ AdornmentEditor _paddingEditor;
+ View _viewToEdit;
+
+ public View ViewToEdit {
+ get => _viewToEdit;
+ set {
+ _origTitle = value.Title;
+ _viewToEdit = value;
+
+ _marginEditor = new AdornmentEditor {
+ X = 0,
+ Y = 0,
+ Title = "Margin",
+ Thickness = _viewToEdit.Margin.Thickness,
+ Color = new Attribute (_viewToEdit.Margin.ColorScheme.Normal),
+ SuperViewRendersLineCanvas = true
+ };
+ _marginEditor.ThicknessChanged += Editor_ThicknessChanged;
+ _marginEditor.AttributeChanged += Editor_AttributeChanged;
+ Add (_marginEditor);
+
+ _borderEditor = new AdornmentEditor {
+ X = Pos.Left (_marginEditor),
+ Y = Pos.Bottom (_marginEditor),
+ Title = "Border",
+ Thickness = _viewToEdit.Border.Thickness,
+ Color = new Attribute (_viewToEdit.Border.ColorScheme.Normal),
+ SuperViewRendersLineCanvas = true
+ };
+ _borderEditor.ThicknessChanged += Editor_ThicknessChanged;
+ _borderEditor.AttributeChanged += Editor_AttributeChanged;
+ Add (_borderEditor);
+
+
+ var borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast ().ToList ();
+ var rbBorderStyle = new RadioGroup (borderStyleEnum.Select (
+ e => e.ToString ()).ToArray ()) {
+
+ X = Pos.Right (_borderEditor) - 1,
+ Y = Pos.Top (_borderEditor),
+ SelectedItem = (int)_viewToEdit.Border.LineStyle,
+ BorderStyle = LineStyle.Double,
+ Title = "Border Style",
+ SuperViewRendersLineCanvas = true
+ };
+ Add (rbBorderStyle);
+
+ rbBorderStyle.SelectedItemChanged += (s, e) => {
+ var prevBorderStyle = _viewToEdit.BorderStyle;
+ _viewToEdit.Border.LineStyle = (LineStyle)e.SelectedItem;
+ if (_viewToEdit.Border.LineStyle == LineStyle.None) {
+ _viewToEdit.Border.Thickness = new Thickness (0);
+ } else if (prevBorderStyle == LineStyle.None && _viewToEdit.Border.LineStyle != LineStyle.None) {
+ _viewToEdit.Border.Thickness = new Thickness (1);
+ }
+ _borderEditor.Thickness = new Thickness (_viewToEdit.Border.Thickness.Left, _viewToEdit.Border.Thickness.Top,
+ _viewToEdit.Border.Thickness.Right, _viewToEdit.Border.Thickness.Bottom);
+ _viewToEdit.SetNeedsDisplay ();
+ LayoutSubviews ();
+ };
+
+ var ckbTitle = new CheckBox ("Show Title") {
+ BorderStyle = LineStyle.Double,
+ X = Pos.Left (_borderEditor),
+ Y = Pos.Bottom (_borderEditor) - 1,
+ Width = Dim.Width (_borderEditor),
+ Checked = true,
+ SuperViewRendersLineCanvas = true
+ };
+ ckbTitle.Toggled += (sender, args) => {
+ if (ckbTitle.Checked == true) {
+ _viewToEdit.Title = _origTitle;
+ } else {
+ _viewToEdit.Title = string.Empty;
+ }
+ };
+ Add (ckbTitle);
+
+ _paddingEditor = new AdornmentEditor {
+ X = Pos.Left (_borderEditor),
+ Y = Pos.Bottom (rbBorderStyle),
+ Title = "Padding",
+ Thickness = _viewToEdit.Padding.Thickness,
+ Color = new Attribute (_viewToEdit.Padding.ColorScheme.Normal),
+ SuperViewRendersLineCanvas = true
+ };
+ _paddingEditor.ThicknessChanged += Editor_ThicknessChanged;
+ _paddingEditor.AttributeChanged += Editor_AttributeChanged;
+ Add (_paddingEditor);
+
+ _diagCheckBox = new CheckBox {
+ Text = "Diagnostics",
+ Y = Pos.Bottom (_paddingEditor)
+ };
+ _diagCheckBox.Toggled += (s, e) => {
+ if (e.NewValue == true) {
+ ConsoleDriver.Diagnostics = ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler;
+ } else {
+ ConsoleDriver.Diagnostics = ConsoleDriver.DiagnosticFlags.Off;
+ }
+ };
+
+ Add (_diagCheckBox);
+ Add (_viewToEdit);
+
+ _viewToEdit.LayoutComplete += (s, e) => {
+ if (ckbTitle.Checked == true) {
+ _viewToEdit.Title = _origTitle;
+ } else {
+ _viewToEdit.Title = string.Empty;
+ }
+ };
+ }
+ }
+
+ void Editor_AttributeChanged (object sender, Attribute attr)
+ {
+ switch (sender.ToString ()) {
+ case var s when s == _marginEditor.ToString ():
+ _viewToEdit.Margin.ColorScheme = new ColorScheme (_viewToEdit.Margin.ColorScheme) { Normal = attr };
+ break;
+ case var s when s == _borderEditor.ToString ():
+ _viewToEdit.Border.ColorScheme = new ColorScheme (_viewToEdit.Border.ColorScheme) { Normal = attr };
+ break;
+ case var s when s == _paddingEditor.ToString ():
+ _viewToEdit.Padding.ColorScheme = new ColorScheme (_viewToEdit.Padding.ColorScheme) { Normal = attr };
+ break;
+ }
+ }
+
+ void Editor_ThicknessChanged (object sender, ThicknessEventArgs e)
+ {
+ try {
+ switch (sender.ToString ()) {
+ case var s when s == _marginEditor.ToString ():
+ _viewToEdit.Margin.Thickness = e.Thickness;
+ break;
+ case var s when s == _borderEditor.ToString ():
+ _viewToEdit.Border.Thickness = e.Thickness;
+ break;
+ case var s when s == _paddingEditor.ToString ():
+ _viewToEdit.Padding.Thickness = e.Thickness;
+ break;
+ }
+ } catch {
+ switch (sender.ToString ()) {
+ case var s when s == _marginEditor.ToString ():
+ _viewToEdit.Margin.Thickness = e.PreviousThickness;
+ break;
+ case var s when s == _borderEditor.ToString ():
+ _viewToEdit.Border.Thickness = e.PreviousThickness;
+ break;
+ case var s when s == _paddingEditor.ToString ():
+ _viewToEdit.Padding.Thickness = e.PreviousThickness;
+ break;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs
index 248e28871..eb4b15c0e 100644
--- a/UICatalog/Scenarios/AllViewsTester.cs
+++ b/UICatalog/Scenarios/AllViewsTester.cs
@@ -72,7 +72,7 @@ public class AllViewsTester : Scenario {
Width = 15,
Height = Dim.Fill (1), // for status bar
CanFocus = false,
- ColorScheme = Colors.TopLevel
+ ColorScheme = Colors.ColorSchemes ["TopLevel"]
};
_classListView = new ListView (_viewClasses.Keys.ToList ()) {
@@ -81,7 +81,7 @@ public class AllViewsTester : Scenario {
Width = Dim.Fill (0),
Height = Dim.Fill (0),
AllowsMarking = false,
- ColorScheme = Colors.TopLevel,
+ ColorScheme = Colors.ColorSchemes ["TopLevel"],
SelectedItem = 0
};
_classListView.OpenSelectedItem += (s, a) => {
@@ -106,7 +106,7 @@ public class AllViewsTester : Scenario {
Width = Dim.Fill (),
Height = 10,
CanFocus = false,
- ColorScheme = Colors.TopLevel
+ ColorScheme = Colors.ColorSchemes ["TopLevel"]
};
_computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 };
_computedCheckBox.Toggled += (s, e) => {
@@ -228,7 +228,7 @@ public class AllViewsTester : Scenario {
Y = Pos.Bottom (_settingsPane),
Width = Dim.Fill (),
Height = Dim.Fill (1), // + 1 for status bar
- ColorScheme = Colors.Dialog
+ ColorScheme = Colors.ColorSchemes ["Dialog"]
};
Application.Top.Add (_leftPane, _settingsPane, _hostPane);
@@ -285,8 +285,8 @@ public class AllViewsTester : Scenario {
}
// TODO: This is missing some
- List _posNames = new() { "Factor", "AnchorEnd", "Center", "Absolute" };
- List _dimNames = new() { "Factor", "Fill", "Absolute" };
+ List _posNames = new () { "Factor", "AnchorEnd", "Center", "Absolute" };
+ List _dimNames = new () { "Factor", "Fill", "Absolute" };
void UpdateSettings (View view)
{
@@ -340,7 +340,7 @@ public class AllViewsTester : Scenario {
// Set the colorscheme to make it stand out if is null by default
if (view.ColorScheme == null) {
- view.ColorScheme = Colors.Base;
+ view.ColorScheme = Colors.ColorSchemes ["Base"];
}
// If the view supports a Text property, set it so we have something to look at
@@ -387,10 +387,10 @@ public class AllViewsTester : Scenario {
//view.X = Pos.Center ();
//view.Y = Pos.Center ();
if (view.Width == null || view.Frame.Width == 0) {
- view.Width = Dim.Fill();
+ view.Width = Dim.Fill ();
}
if (view.Height == null || view.Frame.Height == 0) {
- view.Height = Dim.Fill();
+ view.Height = Dim.Fill ();
}
UpdateSettings (view);
UpdateTitle (view);
diff --git a/UICatalog/Scenarios/AutoSizeAndDirectionText.cs b/UICatalog/Scenarios/AutoSizeAndDirectionText.cs
index 09e66570f..7ef5c039d 100644
--- a/UICatalog/Scenarios/AutoSizeAndDirectionText.cs
+++ b/UICatalog/Scenarios/AutoSizeAndDirectionText.cs
@@ -8,7 +8,7 @@ namespace UICatalog.Scenarios {
{
var text = "Hello World";
var wideText = "Hello World 你";
- var color = Colors.Dialog;
+ var color = Colors.ColorSchemes ["Dialog"];
var labelH = new Label (text, TextDirection.LeftRight_TopBottom) {
X = 1,
diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
index e2165e4ce..63e635e3e 100644
--- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs
+++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
@@ -169,7 +169,7 @@ namespace UICatalog.Scenarios {
Width = Dim.Percent (80);
Height = Dim.Percent (50);
- ColorScheme = Colors.Base;
+ ColorScheme = Colors.ColorSchemes ["Base"];
var label = new Label ("Worker collection Log") {
X = Pos.Center (),
@@ -308,14 +308,14 @@ namespace UICatalog.Scenarios {
Width = Dim.Percent (85);
Height = Dim.Percent (85);
- ColorScheme = Colors.Dialog;
+ ColorScheme = Colors.ColorSchemes ["Dialog"];
Title = "Run Worker";
label = new Label ("Press start to do the work or close to quit.") {
X = Pos.Center (),
Y = 1,
- ColorScheme = Colors.Dialog
+ ColorScheme = Colors.ColorSchemes ["Dialog"]
};
Add (label);
diff --git a/UICatalog/Scenarios/BasicColors.cs b/UICatalog/Scenarios/BasicColors.cs
index e99c88db3..e0de4019e 100644
--- a/UICatalog/Scenarios/BasicColors.cs
+++ b/UICatalog/Scenarios/BasicColors.cs
@@ -91,9 +91,10 @@ namespace UICatalog.Scenarios {
var fore = e.MouseEvent.View.GetNormalColor ().Foreground;
var back = e.MouseEvent.View.GetNormalColor ().Background;
lblForeground.Text = $"#{fore.R:X2}{fore.G:X2}{fore.B:X2} {fore.ColorName} ";
- viewForeground.ColorScheme.Normal = new Attribute (fore, fore);
+ viewForeground.ColorScheme = new ColorScheme (viewForeground.ColorScheme) { Normal = new Attribute (fore, fore) };
+
lblBackground.Text = $"#{back.R:X2}{back.G:X2}{back.B:X2} {back.ColorName} ";
- viewBackground.ColorScheme.Normal = new Attribute (back, back);
+ viewBackground.ColorScheme = new ColorScheme (viewBackground.ColorScheme) { Normal = new Attribute (back, back) };
}
};
}
diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs
index 41bc328a4..6966cd857 100644
--- a/UICatalog/Scenarios/Buttons.cs
+++ b/UICatalog/Scenarios/Buttons.cs
@@ -99,7 +99,7 @@ public class Buttons : Scenario {
var removeButton = new Button ("Remove this button") {
X = 2,
Y = Pos.Bottom (button) + 1,
- ColorScheme = Colors.Error
+ ColorScheme = Colors.ColorSchemes ["Error"]
};
Win.Add (removeButton);
// This in interesting test case because `moveBtn` and below are laid out relative to this one!
@@ -123,7 +123,7 @@ public class Buttons : Scenario {
X = 0,
Y = Pos.Center () - 1,
Width = 30,
- ColorScheme = Colors.Error,
+ ColorScheme = Colors.ColorSchemes ["Error"],
};
moveBtn.Clicked += (s, e) => {
moveBtn.X = moveBtn.Frame.X + 5;
@@ -137,7 +137,7 @@ public class Buttons : Scenario {
X = 0,
Y = Pos.Center () + 1,
Width = 30,
- ColorScheme = Colors.Error,
+ ColorScheme = Colors.ColorSchemes ["Error"],
};
sizeBtn.Clicked += (s, e) => {
sizeBtn.Width = sizeBtn.Frame.Width + 5;
@@ -155,7 +155,7 @@ public class Buttons : Scenario {
// Demonstrates how changing the View.Frame property can move Views
var moveBtnA = new Button (0, 0, "Move This Button via Frame") {
- ColorScheme = Colors.Error,
+ ColorScheme = Colors.ColorSchemes ["Error"],
};
moveBtnA.Clicked += (s, e) => {
moveBtnA.Frame = new Rect (moveBtnA.Frame.X + 5, moveBtnA.Frame.Y, moveBtnA.Frame.Width, moveBtnA.Frame.Height);
@@ -164,7 +164,7 @@ public class Buttons : Scenario {
// Demonstrates how changing the View.Frame property can SIZE Views (#583)
var sizeBtnA = new Button (0, 2, " ~ s gui.cs master ↑10 = Со_хранить") {
- ColorScheme = Colors.Error,
+ ColorScheme = Colors.ColorSchemes ["Error"],
};
sizeBtnA.Clicked += (s, e) => {
sizeBtnA.Frame = new Rect (sizeBtnA.Frame.X, sizeBtnA.Frame.Y, sizeBtnA.Frame.Width + 5, sizeBtnA.Frame.Height);
@@ -215,7 +215,7 @@ public class Buttons : Scenario {
X = 2,
Y = Pos.Bottom (radioGroup) + 1,
Width = Dim.Width (computedFrame) - 2,
- ColorScheme = Colors.TopLevel,
+ ColorScheme = Colors.ColorSchemes ["TopLevel"],
};
moveHotKeyBtn.Clicked += (s, e) => {
moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text);
@@ -227,7 +227,7 @@ public class Buttons : Scenario {
X = Pos.Left (absoluteFrame) + 1,
Y = Pos.Bottom (radioGroup) + 1,
Width = Dim.Width (absoluteFrame) - 2, // BUGBUG: Not always the width isn't calculated correctly.
- ColorScheme = Colors.TopLevel,
+ ColorScheme = Colors.ColorSchemes ["TopLevel"],
};
moveUnicodeHotKeyBtn.Clicked += (s, e) => {
moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text);
diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs
index de4204b7e..fa52ce9d2 100644
--- a/UICatalog/Scenarios/CharacterMap.cs
+++ b/UICatalog/Scenarios/CharacterMap.cs
@@ -36,7 +36,7 @@ public class CharacterMap : Scenario {
public override void Init ()
{
Application.Init ();
- Application.Top.ColorScheme = Colors.Base;
+ Application.Top.ColorScheme = Colors.ColorSchemes ["Base"];
}
public override void Setup ()
@@ -330,7 +330,7 @@ class CharMap : ScrollView {
public CharMap ()
{
- ColorScheme = Colors.Dialog;
+ ColorScheme = Colors.ColorSchemes ["Dialog"];
CanFocus = true;
ContentSize = new Size (RowWidth, (int)((MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)) * _rowHeight));
diff --git a/UICatalog/Scenarios/Clipping.cs b/UICatalog/Scenarios/Clipping.cs
index 0d9d8e526..36d815f61 100644
--- a/UICatalog/Scenarios/Clipping.cs
+++ b/UICatalog/Scenarios/Clipping.cs
@@ -10,7 +10,7 @@ namespace UICatalog.Scenarios {
public override void Init ()
{
Application.Init ();
- Application.Top.ColorScheme = Colors.Base;
+ Application.Top.ColorScheme = Colors.ColorSchemes ["Base"];
}
public override void Setup ()
@@ -21,12 +21,12 @@ namespace UICatalog.Scenarios {
//Win.Height = Dim.Fill () - 2;
var label = new Label ("ScrollView (new Rect (3, 3, 50, 20)) with a 200, 100 ContentSize...") {
X = 0, Y = 0,
- //ColorScheme = Colors.Dialog
+ //ColorScheme = Colors.ColorSchemes ["Dialog"]
};
Application.Top.Add (label);
var scrollView = new ScrollView (new Rect (3, 3, 50, 20));
- scrollView.ColorScheme = Colors.Menu;
+ scrollView.ColorScheme = Colors.ColorSchemes ["Menu"];
scrollView.ContentSize = new Size (200, 100);
//ContentOffset = new Point (0, 0),
//scrollView.ShowVerticalScrollIndicator = true;
@@ -38,7 +38,7 @@ namespace UICatalog.Scenarios {
Y = 3,
Width = Dim.Fill (3),
Height = Dim.Fill (3),
- ColorScheme = Colors.Dialog,
+ ColorScheme = Colors.ColorSchemes ["Dialog"],
Id = "1"
};
@@ -48,7 +48,7 @@ namespace UICatalog.Scenarios {
Y = 3,
Width = Dim.Fill (3),
Height = Dim.Fill (3),
- ColorScheme = Colors.Error,
+ ColorScheme = Colors.ColorSchemes ["Error"],
Id = "2"
};
embedded1.Add (embedded2);
@@ -59,7 +59,7 @@ namespace UICatalog.Scenarios {
Y = 3,
Width = Dim.Fill (3),
Height = Dim.Fill (3),
- ColorScheme = Colors.TopLevel,
+ ColorScheme = Colors.ColorSchemes ["TopLevel"],
Id = "3"
};
diff --git a/UICatalog/Scenarios/CollectionNavigatorTester.cs b/UICatalog/Scenarios/CollectionNavigatorTester.cs
index ce1a8444f..207093a2b 100644
--- a/UICatalog/Scenarios/CollectionNavigatorTester.cs
+++ b/UICatalog/Scenarios/CollectionNavigatorTester.cs
@@ -16,7 +16,7 @@ namespace UICatalog.Scenarios {
public override void Init ()
{
Application.Init ();
- Application.Top.ColorScheme = Colors.Base;
+ Application.Top.ColorScheme = Colors.ColorSchemes ["Base"];
}
System.Collections.Generic.List _items = new string [] {
diff --git a/UICatalog/Scenarios/ColorPicker.cs b/UICatalog/Scenarios/ColorPicker.cs
index 8e1e991b5..c2a58427a 100644
--- a/UICatalog/Scenarios/ColorPicker.cs
+++ b/UICatalog/Scenarios/ColorPicker.cs
@@ -1,132 +1,129 @@
-using Terminal.Gui;
-using System;
+using System;
+using Terminal.Gui;
-namespace UICatalog.Scenarios {
- [ScenarioMetadata (Name: "Color Picker", Description: "Color Picker.")]
- [ScenarioCategory ("Colors")]
- [ScenarioCategory ("Controls")]
- public class ColorPickers : Scenario {
- ///
- /// Foreground ColorPicker.
- ///
- private ColorPicker foregroundColorPicker;
+namespace UICatalog.Scenarios;
- ///
- /// Background ColorPicker.
- ///
- private ColorPicker backgroundColorPicker;
+[ScenarioMetadata ("Color Picker", "Color Picker.")]
+[ScenarioCategory ("Colors")]
+[ScenarioCategory ("Controls")]
+public class ColorPickers : Scenario {
- ///
- /// Foreground color label.
- ///
- private Label _foregroundColorLabel;
+ ///
+ /// Background color Label.
+ ///
+ Label _backgroundColorLabel;
- ///
- /// Background color Label.
- ///
- private Label _backgroundColorLabel;
+ ///
+ /// Demo label.
+ ///
+ View _demoView;
- ///
- /// Demo label.
- ///
- private View _demoView;
+ ///
+ /// Foreground color label.
+ ///
+ Label _foregroundColorLabel;
- ///
- /// Setup the scenario.
- ///
- public override void Setup ()
- {
- // Scenario Window's.
- Win.Title = this.GetName ();
+ ///
+ /// Background ColorPicker.
+ ///
+ ColorPicker backgroundColorPicker;
- // Foreground ColorPicker.
- foregroundColorPicker = new ColorPicker () {
- Title = "Foreground Color",
- X = 0,
- Y = 0,
- BoxHeight = 3,
- BoxWidth = 6,
- BorderStyle = LineStyle.Single
- };
- foregroundColorPicker.ColorChanged += ForegroundColor_ColorChanged;
- Win.Add (foregroundColorPicker);
+ ///
+ /// Foreground ColorPicker.
+ ///
+ ColorPicker foregroundColorPicker;
- _foregroundColorLabel = new Label {
- X = Pos.Left (foregroundColorPicker),
- Y = Pos.Bottom (foregroundColorPicker) + 1
- };
- Win.Add (_foregroundColorLabel);
+ ///
+ /// Setup the scenario.
+ ///
+ public override void Setup ()
+ {
+ // Scenario Window's.
+ Win.Title = GetName ();
- // Background ColorPicker.
- backgroundColorPicker = new ColorPicker () {
- Title = "Background Color",
- Y = 0,
- X = 0,
- BoxHeight = 1,
- BoxWidth = 4,
- BorderStyle = LineStyle.Single
- };
- backgroundColorPicker.X = Pos.AnchorEnd () - (Pos.Right (backgroundColorPicker) - Pos.Left (backgroundColorPicker));
- backgroundColorPicker.ColorChanged += BackgroundColor_ColorChanged;
- Win.Add (backgroundColorPicker);
- _backgroundColorLabel = new Label ();
- _backgroundColorLabel.X = Pos.AnchorEnd () - (Pos.Right (_backgroundColorLabel) - Pos.Left (_backgroundColorLabel));
- _backgroundColorLabel.Y = Pos.Bottom (backgroundColorPicker) + 1;
- Win.Add (_backgroundColorLabel);
-
- // Demo Label.
- _demoView = new View () {
- Title = "Color Sample",
- Text = "Lorem Ipsum",
- TextAlignment = TextAlignment.Centered,
- VerticalTextAlignment = VerticalTextAlignment.Middle,
- BorderStyle = LineStyle.Heavy,
- X = Pos.Center (),
- Y = Pos.Center (),
- Height = 5,
- Width = 20
- };
- Win.Add (_demoView);
-
- // Set default colors.
- foregroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Foreground.ColorName;
- backgroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Background.ColorName;
- Win.Initialized += (s, e) => Win.LayoutSubviews ();
- }
-
- ///
- /// Fired when foreground color is changed.
- ///
- private void ForegroundColor_ColorChanged (object sender, EventArgs e)
- {
- UpdateColorLabel (_foregroundColorLabel, foregroundColorPicker);
- UpdateDemoLabel ();
- }
-
- ///
- /// Fired when background color is changed.
- ///
- private void BackgroundColor_ColorChanged (object sender, EventArgs e)
- {
- UpdateColorLabel (_backgroundColorLabel, backgroundColorPicker);
- UpdateDemoLabel ();
- }
-
- ///
- /// Update a color label from his ColorPicker.
- ///
- private void UpdateColorLabel (Label label, ColorPicker colorPicker)
- {
- label.Clear ();
- var color = new Color (colorPicker.SelectedColor);
- label.Text = $"{colorPicker.SelectedColor} ({(int)colorPicker.SelectedColor}) #{color.R:X2}{color.G:X2}{color.B:X2}";
- }
-
- ///
- /// Update Demo Label.
- ///
- private void UpdateDemoLabel () => _demoView.ColorScheme = new ColorScheme () {
- Normal = new Attribute (foregroundColorPicker.SelectedColor, backgroundColorPicker.SelectedColor)
+ // Foreground ColorPicker.
+ foregroundColorPicker = new ColorPicker {
+ Title = "Foreground Color",
+ BorderStyle = LineStyle.Single
};
+ foregroundColorPicker.ColorChanged += ForegroundColor_ColorChanged;
+ Win.Add (foregroundColorPicker);
+
+ _foregroundColorLabel = new Label {
+ X = Pos.Left (foregroundColorPicker),
+ Y = Pos.Bottom (foregroundColorPicker) + 1
+ };
+ Win.Add (_foregroundColorLabel);
+
+ // Background ColorPicker.
+ backgroundColorPicker = new ColorPicker {
+ Title = "Background Color",
+ Y = Pos.Center (),
+ X = Pos.Center (),
+ BoxHeight = 1,
+ BoxWidth = 4,
+ BorderStyle = LineStyle.Single
+ };
+ //backgroundColorPicker.X = Pos.AnchorEnd () - (Pos.Right (backgroundColorPicker) - Pos.Left (backgroundColorPicker));
+ backgroundColorPicker.ColorChanged += BackgroundColor_ColorChanged;
+ Win.Add (backgroundColorPicker);
+ _backgroundColorLabel = new Label ();
+ _backgroundColorLabel.X = Pos.AnchorEnd () - (Pos.Right (_backgroundColorLabel) - Pos.Left (_backgroundColorLabel));
+ _backgroundColorLabel.Y = Pos.Bottom (backgroundColorPicker) + 1;
+ Win.Add (_backgroundColorLabel);
+
+ // Demo Label.
+ _demoView = new View {
+ Title = "Color Sample",
+ Text = "Lorem Ipsum",
+ TextAlignment = TextAlignment.Centered,
+ VerticalTextAlignment = VerticalTextAlignment.Middle,
+ BorderStyle = LineStyle.Heavy,
+ X = Pos.Center (),
+ Y = Pos.Center (),
+ Height = 5,
+ Width = 20
+ };
+ Win.Add (_demoView);
+
+ // Set default colors.
+ foregroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Foreground.ColorName;
+ backgroundColorPicker.SelectedColor = _demoView.SuperView.ColorScheme.Normal.Background.ColorName;
+ Win.Initialized += (s, e) => Win.LayoutSubviews ();
}
+
+ ///
+ /// Fired when foreground color is changed.
+ ///
+ void ForegroundColor_ColorChanged (object sender, EventArgs e)
+ {
+ UpdateColorLabel (_foregroundColorLabel, foregroundColorPicker);
+ UpdateDemoLabel ();
+ }
+
+ ///
+ /// Fired when background color is changed.
+ ///
+ void BackgroundColor_ColorChanged (object sender, EventArgs e)
+ {
+ UpdateColorLabel (_backgroundColorLabel, backgroundColorPicker);
+ UpdateDemoLabel ();
+ }
+
+ ///
+ /// Update a color label from his ColorPicker.
+ ///
+ void UpdateColorLabel (Label label, ColorPicker colorPicker)
+ {
+ label.Clear ();
+ var color = new Color (colorPicker.SelectedColor);
+ label.Text = $"{colorPicker.SelectedColor} ({(int)colorPicker.SelectedColor}) #{color.R:X2}{color.G:X2}{color.B:X2}";
+ }
+
+ ///
+ /// Update Demo Label.
+ ///
+ void UpdateDemoLabel () => _demoView.ColorScheme = new ColorScheme {
+ Normal = new Attribute (foregroundColorPicker.SelectedColor, backgroundColorPicker.SelectedColor)
+ };
}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/ComboBoxIteration.cs b/UICatalog/Scenarios/ComboBoxIteration.cs
index d3062763b..3158fe83d 100644
--- a/UICatalog/Scenarios/ComboBoxIteration.cs
+++ b/UICatalog/Scenarios/ComboBoxIteration.cs
@@ -24,7 +24,7 @@ namespace UICatalog.Scenarios {
Win.Add (listview);
var lbComboBox = new Label () {
- ColorScheme = Colors.TopLevel,
+ ColorScheme = Colors.ColorSchemes ["TopLevel"],
X = Pos.Right (lbListView) + 1,
Width = Dim.Percent (40)
};
diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs
index c1f24a17c..c135be439 100644
--- a/UICatalog/Scenarios/ComputedLayout.cs
+++ b/UICatalog/Scenarios/ComputedLayout.cs
@@ -33,7 +33,7 @@ namespace UICatalog.Scenarios {
Y = 0,
Width = Dim.Fill (),
Height = 1,
- ColorScheme = Colors.Error
+ ColorScheme = Colors.ColorSchemes ["Error"]
};
Application.Top.Add (horizontalRuler);
@@ -47,7 +47,7 @@ namespace UICatalog.Scenarios {
Y = 0,
Width = 1,
Height = Dim.Fill (),
- ColorScheme = Colors.Error
+ ColorScheme = Colors.ColorSchemes ["Error"]
};
Application.Top.LayoutComplete += (s, a) => {
@@ -88,10 +88,10 @@ namespace UICatalog.Scenarios {
string txt = "Resize the terminal to see computed layout in action.";
var labelList = new List