mirror of
				https://github.com/9ParsonsB/Pulsar.git
				synced 2025-10-31 06:34:58 -04:00 
			
		
		
		
	[Core] Settings improvements: Grouping w/labels, support for doubles (#137)
* [Core] Settings improvements: Grouping w/labels, support for doubles
Layout improvements:
* Plugins can set `[SettingSuggestedColumnWidth(123)]` on a settings class to adjust the settings view column width to make it wider or narrower to fit things nicely.
* Plugins can set `[SettingNewGroup("label")]` on any setting property to force a "new paragraph", or group, of settings. If "label" is also provided, a grouping header with that text will also be created.
* A double precision up/down numeric control has been added. In support of this, there is now a precision value on the existing `[SettingNumericBounds]` attribute to specify the number of digits of precision the control displays/allows.
Plugins:
* The above have been applied/demonstrated on the Botanist and Explorer plugin settings.
			
			
This commit is contained in:
		| @@ -7,11 +7,12 @@ using System.Threading.Tasks; | ||||
|  | ||||
| namespace Observatory.Botanist | ||||
| { | ||||
|     [SettingSuggestedColumnWidth(450)] | ||||
|     class BotanistSettings | ||||
|     { | ||||
|         [SettingDisplayName("Enable Sampler Status Overlay")] | ||||
|         public bool OverlayEnabled { get; set; } | ||||
|         [SettingDisplayName("Sampler Status Overlay is sticky until sampling complete (if enabled)")] | ||||
|         [SettingDisplayName("Status Overlay is sticky until sampling is complete")] | ||||
|         public bool OverlayIsSticky { get; set; } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -26,6 +26,12 @@ namespace Observatory.UI | ||||
|             _plugin = plugin; | ||||
|  | ||||
|             // Filtered to only settings without SettingIgnore attribute | ||||
|             var attrib = _plugin.Settings.GetType().GetCustomAttribute<SettingSuggestedColumnWidth>(); | ||||
|             if (attrib != null && attrib.Width > 0) | ||||
|             { | ||||
|                 int minScreenWidth = Screen.AllScreens.Min(s => s.Bounds.Width); | ||||
|                 _colWidth = Math.Min(attrib.Width, minScreenWidth / 2); | ||||
|             } | ||||
|             var settings = PluginManagement.PluginManager.GetSettingDisplayNames(plugin.Settings).Where(s => !Attribute.IsDefined(s.Key, typeof(SettingIgnore))); | ||||
|             CreateControls(settings); | ||||
|  | ||||
| @@ -51,12 +57,30 @@ namespace Observatory.UI | ||||
|  | ||||
|             foreach (var setting in settings) | ||||
|             { | ||||
|                 // Reset the column tracking for checkboxes if this isn't a checkbox | ||||
|                 // Reset the column tracking for checkboxes if this isn't a checkbox or explicitly requested | ||||
|                 // to start a new grouping of settings. | ||||
|                 int addedHeight = 35; | ||||
|                 if (setting.Key.PropertyType.Name != "Boolean") | ||||
|                 var newGroup = Attribute.GetCustomAttribute(setting.Key, typeof(SettingNewGroup)) as SettingNewGroup; | ||||
|  | ||||
|                 if (setting.Key.PropertyType.Name != "Boolean" || newGroup != null) | ||||
|                 { | ||||
|                     if (recentHalfCol) _colHeight.Add(addedHeight); | ||||
|                     recentHalfCol = false; | ||||
|  | ||||
|                     if (newGroup != null) | ||||
|                     { | ||||
|                         if (!string.IsNullOrEmpty(newGroup.Label)) | ||||
|                         { | ||||
|                             var label = CreateGroupLabel(newGroup.Label); | ||||
|                             label.Location = GetSettingPosition(); | ||||
|  | ||||
|                             Controls.Add(label); | ||||
|                             trackBottomEdge(label); | ||||
|                             _colHeight.Add(label.Height); | ||||
|                         } | ||||
|                         else | ||||
|                             _colHeight.Add(10); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 switch (setting.Key.GetValue(_plugin.Settings)) | ||||
| @@ -98,7 +122,7 @@ namespace Observatory.UI | ||||
|                         break; | ||||
|                     case int: | ||||
|                         // We have two options for integer values: | ||||
|                         // 1) A slider (explicit by way of the SettingIntegerUseSlider attribute and bounded to 0..100 by default) | ||||
|                         // 1) A slider (explicit by way of the SettingNumericUseSlider attribute and bounded to 0..100 by default) | ||||
|                         // 2) A numeric up/down (default otherwise, and is unbounded by default). | ||||
|                         // Bounds for both can be set via the SettingNumericBounds attribute, only the up/down uses Increment. | ||||
|                         var intLabel = CreateSettingLabel(setting.Value); | ||||
| @@ -109,7 +133,7 @@ namespace Observatory.UI | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             intControl = CreateSettingNumericUpDown(setting.Key); | ||||
|                             intControl = CreateSettingNumericUpDownForInt(setting.Key); | ||||
|                         } | ||||
|                         intLabel.Location = GetSettingPosition(); | ||||
|                         intControl.Location = GetSettingPosition(true); | ||||
| @@ -122,6 +146,23 @@ namespace Observatory.UI | ||||
|                         Controls.Add(intControl); | ||||
|                         trackBottomEdge(intControl); | ||||
|                         break; | ||||
|                     case double: | ||||
|                         // We have one options for double values: | ||||
|                         // 1) A numeric up/down (default otherwise, and is unbounded by default). | ||||
|                         // Bounds can be set via the SettingNumericBounds attribute. | ||||
|                         var doubleLabel = CreateSettingLabel(setting.Value); | ||||
|                         Control doubleControl = CreateSettingNumericUpDownForDouble(setting.Key); | ||||
|                         doubleLabel.Location = GetSettingPosition(); | ||||
|                         doubleControl.Location = GetSettingPosition(true); | ||||
|  | ||||
|                         addedHeight = doubleControl.Height + 2; | ||||
|                         doubleLabel.Height = doubleControl.Height; | ||||
|                         doubleLabel.TextAlign = ContentAlignment.MiddleRight; | ||||
|  | ||||
|                         Controls.Add(doubleLabel); | ||||
|                         Controls.Add(doubleControl); | ||||
|                         trackBottomEdge(doubleControl); | ||||
|                         break; | ||||
|                     case Action action: | ||||
|                         var button = CreateSettingButton(setting.Value, action); | ||||
|  | ||||
| @@ -152,8 +193,7 @@ namespace Observatory.UI | ||||
|  | ||||
|         private Point GetSettingPosition(bool secondCol = false) | ||||
|         { | ||||
|             //return new Point(10 + (secondCol ? 200 : 0), -26 + _colHeight.Sum()); | ||||
|             return new Point(10 + (secondCol ? _colWidth : 0), 15 + _colHeight.Sum()); | ||||
|             return new Point(20 + (secondCol ? _colWidth + 20 : 0), 15 + _colHeight.Sum()); | ||||
|         } | ||||
|  | ||||
|  | ||||
| @@ -170,6 +210,20 @@ namespace Observatory.UI | ||||
|             return label; | ||||
|         } | ||||
|  | ||||
|         private Label CreateGroupLabel(string groupLabel) | ||||
|         { | ||||
|             Label label = new() | ||||
|             { | ||||
|                 Text = groupLabel, | ||||
|                 TextAlign = System.Drawing.ContentAlignment.MiddleLeft, | ||||
|                 Width = _colWidth * 2, | ||||
|                 ForeColor = Color.LightGray, | ||||
|             }; | ||||
|             label.Font = new Font(label.Font.FontFamily, label.Font.Size + 1, FontStyle.Bold); | ||||
|             label.Height += 10; // Add spacing. | ||||
|             return label; | ||||
|         } | ||||
|  | ||||
|         private ComboBox CreateSettingDropdown(PropertyInfo setting, Dictionary<string, object> dropdownItems) | ||||
|         { | ||||
|             var backingValueName = (SettingBackingValue?)Attribute.GetCustomAttribute(setting, typeof(SettingBackingValue)); | ||||
| @@ -225,7 +279,7 @@ namespace Observatory.UI | ||||
|  | ||||
|         private TrackBar CreateSettingTrackbar(PropertyInfo setting) | ||||
|         { | ||||
|             SettingNumericBounds? bounds = (SettingNumericBounds?)System.Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds)); | ||||
|             SettingNumericBounds? bounds = (SettingNumericBounds?)Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds)); | ||||
|  | ||||
|             var minBound = Convert.ToInt32(bounds?.Minimum ?? 0); | ||||
|             var maxBound = Convert.ToInt32(bounds?.Maximum ?? 100); | ||||
| @@ -246,16 +300,16 @@ namespace Observatory.UI | ||||
|  | ||||
|             trackBar.ValueChanged += (sender, e) => | ||||
|             { | ||||
|                 setting.SetValue(_plugin.Settings, Convert.ToInt32(trackBar.Value)); | ||||
|                 setting.SetValue(_plugin.Settings, trackBar.Value); | ||||
|                 SaveSettings(); | ||||
|             }; | ||||
|  | ||||
|             return trackBar; | ||||
|         } | ||||
|  | ||||
|         private NumericUpDown CreateSettingNumericUpDown(PropertyInfo setting) | ||||
|         private NumericUpDown CreateSettingNumericUpDownForInt(PropertyInfo setting) | ||||
|         { | ||||
|             SettingNumericBounds? bounds = (SettingNumericBounds?)System.Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds)); | ||||
|             SettingNumericBounds? bounds = (SettingNumericBounds?)Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds)); | ||||
|             NumericUpDown numericUpDown = new() | ||||
|             { | ||||
|                 Width = _colWidth, | ||||
| @@ -265,7 +319,6 @@ namespace Observatory.UI | ||||
|             }; | ||||
|  | ||||
|             numericUpDown.Value = (int?)setting.GetValue(_plugin.Settings) ?? 0; | ||||
|  | ||||
|             numericUpDown.ValueChanged += (sender, e) => | ||||
|             { | ||||
|                 setting.SetValue(_plugin.Settings, Convert.ToInt32(numericUpDown.Value)); | ||||
| @@ -275,6 +328,28 @@ namespace Observatory.UI | ||||
|             return numericUpDown; | ||||
|         } | ||||
|  | ||||
|         private NumericUpDown CreateSettingNumericUpDownForDouble(PropertyInfo setting) | ||||
|         { | ||||
|             SettingNumericBounds? bounds = (SettingNumericBounds?)Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds)); | ||||
|             NumericUpDown numericUpDown = new() | ||||
|             { | ||||
|                 Width = _colWidth, | ||||
|                 Minimum = Convert.ToDecimal(bounds?.Minimum ?? Double.MinValue), | ||||
|                 Maximum = Convert.ToDecimal(bounds?.Maximum ?? Double.MaxValue), | ||||
|                 Increment = Convert.ToDecimal(bounds?.Increment ?? 1.0), | ||||
|                 DecimalPlaces = bounds?.Precision ?? 1, | ||||
|             }; | ||||
|  | ||||
|             numericUpDown.Value = Convert.ToDecimal(setting.GetValue(_plugin.Settings) ?? 0); | ||||
|             numericUpDown.ValueChanged += (sender, e) => | ||||
|             { | ||||
|                 setting.SetValue(_plugin.Settings, Convert.ToDouble(numericUpDown.Value)); | ||||
|                 SaveSettings(); | ||||
|             }; | ||||
|  | ||||
|             return numericUpDown; | ||||
|         } | ||||
|  | ||||
|         private CheckBox CreateBoolSetting(KeyValuePair<PropertyInfo, string> setting) | ||||
|         { | ||||
|             CheckBox checkBox = new() | ||||
|   | ||||
| @@ -1,12 +1,14 @@ | ||||
| using Observatory.Framework; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace Observatory.Explorer | ||||
| { | ||||
|     [SettingSuggestedColumnWidth(300)] | ||||
|     public class ExplorerSettings | ||||
|     { | ||||
|         public ExplorerSettings() | ||||
| @@ -14,6 +16,12 @@ namespace Observatory.Explorer | ||||
|             CustomCriteriaFile = $"{Environment.GetFolderPath(Environment.SpecialFolder.Personal)}{System.IO.Path.DirectorySeparatorChar}ObservatoryCriteria.lua"; | ||||
|         } | ||||
|  | ||||
|         [SettingNewGroup("Display")] | ||||
|         [SettingDisplayName("Only Show Current System")] | ||||
|         public bool OnlyShowCurrentSystem { get; set; } | ||||
|  | ||||
|  | ||||
|         [SettingNewGroup("Built-in Criteria")] | ||||
|         [SettingDisplayName("Landable & Terraformable")] | ||||
|         public bool LandableTerraformable { get; set; } | ||||
|  | ||||
| @@ -83,12 +91,9 @@ namespace Observatory.Explorer | ||||
|         [SettingDisplayName("High-Value Body")] | ||||
|         public bool HighValueMappable { get; set; } | ||||
|  | ||||
|         [SettingNewGroup("Custom Criteria")] | ||||
|         [SettingDisplayName("Enable Custom Criteria")] | ||||
|         public bool EnableCustomCriteria { get; set; } | ||||
|  | ||||
|         [SettingDisplayName("Only Show Current System")] | ||||
|         public bool OnlyShowCurrentSystem { get; set; } | ||||
|  | ||||
|         [SettingDisplayName("Custom Criteria File")] | ||||
|         [System.Text.Json.Serialization.JsonIgnore] | ||||
|         public System.IO.FileInfo CustomCriteria {get => new System.IO.FileInfo(CustomCriteriaFile); set => CustomCriteriaFile = value.FullName;} | ||||
|   | ||||
| @@ -6,6 +6,32 @@ using System.Threading.Tasks; | ||||
|  | ||||
| namespace Observatory.Framework | ||||
| { | ||||
|  | ||||
|     #region Settings class attributes | ||||
|     /// <summary> | ||||
|     /// Specifies the width of a settings column in the settings view. There are two columns. | ||||
|     /// </summary> | ||||
|     [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] | ||||
|     public class SettingSuggestedColumnWidth : Attribute | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Specifies the width of a settings column in the settings view. There are two columns. | ||||
|         /// </summary> | ||||
|         /// <param name="width">Provides a hint of the width of a settings column.</param> | ||||
|         public SettingSuggestedColumnWidth(int width) | ||||
|         { | ||||
|             Width = width; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Provides a hint of the width of a settings column. | ||||
|         /// </summary> | ||||
|         public int Width { get; } | ||||
|     } | ||||
|     #endregion | ||||
|  | ||||
|     #region Setting property attributes | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Specifies text to display as the name of the setting in the UI instead of the property name. | ||||
|     /// </summary> | ||||
| @@ -34,17 +60,24 @@ namespace Observatory.Framework | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Suggests default column width when building basic UI | ||||
|     /// Starts a new visual group of settings beginning with the current setting with an optional label. | ||||
|     /// </summary> | ||||
|     [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] | ||||
|     public class ColumnSuggestedWidth : Attribute | ||||
|     {  | ||||
|         public ColumnSuggestedWidth(int width) | ||||
|     public class SettingNewGroup : Attribute | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Starts a new visual group of settings beginning with the current setting with an optional label. | ||||
|         /// </summary> | ||||
|         /// <param name="label">An optional label describing the group.</param> | ||||
|         public SettingNewGroup(string label = "") | ||||
|         { | ||||
|             Width = width; | ||||
|             Label = label; | ||||
|         } | ||||
|  | ||||
|         public int Width { get; }  | ||||
|         /// <summary> | ||||
|         /// An optional label describing the group. | ||||
|         /// </summary> | ||||
|         public string Label { get; } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -97,6 +130,7 @@ namespace Observatory.Framework | ||||
|         private double minimum; | ||||
|         private double maximum; | ||||
|         private double increment; | ||||
|         private int precision; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Specify bounds for numeric inputs. | ||||
| @@ -104,11 +138,13 @@ namespace Observatory.Framework | ||||
|         /// <param name="minimum">Minimum allowed value.</param> | ||||
|         /// <param name="maximum">Maximum allowed value.</param> | ||||
|         /// <param name="increment">Increment between allowed values in slider/roller inputs.</param> | ||||
|         public SettingNumericBounds(double minimum, double maximum, double increment = 1.0) | ||||
|         /// <param name="precision">The number of digits to display for non integer values.</param> | ||||
|         public SettingNumericBounds(double minimum, double maximum, double increment = 1.0, int precision = 1) | ||||
|         { | ||||
|             this.minimum = minimum; | ||||
|             this.maximum = maximum; | ||||
|             this.increment = increment; | ||||
|             this.precision = precision; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
| @@ -121,7 +157,7 @@ namespace Observatory.Framework | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Maxunyn allowed value. | ||||
|         /// Maximum allowed value. | ||||
|         /// </summary> | ||||
|         public double Maximum | ||||
|         { | ||||
| @@ -137,5 +173,38 @@ namespace Observatory.Framework | ||||
|             get => increment; | ||||
|             set => increment = value; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The number of digits to display for non integer values. | ||||
|         /// </summary> | ||||
|         public int Precision | ||||
|         { | ||||
|             get => precision; | ||||
|             set => precision = value; | ||||
|         } | ||||
|     } | ||||
|     #endregion | ||||
|  | ||||
|     #region BasicUI attributes | ||||
|     /// <summary> | ||||
|     /// Suggests default column width when building basic plugin grid UI. | ||||
|     /// </summary> | ||||
|     [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] | ||||
|     public class ColumnSuggestedWidth : Attribute | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Suggests default column width when building basic plugin grid UI. | ||||
|         /// </summary> | ||||
|         /// <param name="width">The suggested width of the annotated column.</param> | ||||
|         public ColumnSuggestedWidth(int width) | ||||
|         { | ||||
|             Width = width; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The suggested width of the annotated column. | ||||
|         /// </summary> | ||||
|         public int Width { get; } | ||||
|     } | ||||
|     #endregion | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,22 @@ | ||||
|         <name>ObservatoryFramework</name> | ||||
|     </assembly> | ||||
|     <members> | ||||
|         <member name="T:Observatory.Framework.SettingSuggestedColumnWidth"> | ||||
|             <summary> | ||||
|             Specifies the width of a settings column in the settings view. There are two columns. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:Observatory.Framework.SettingSuggestedColumnWidth.#ctor(System.Int32)"> | ||||
|             <summary> | ||||
|             Specifies the width of a settings column in the settings view. There are two columns. | ||||
|             </summary> | ||||
|             <param name="width">Provides a hint of the width of a settings column.</param> | ||||
|         </member> | ||||
|         <member name="P:Observatory.Framework.SettingSuggestedColumnWidth.Width"> | ||||
|             <summary> | ||||
|             Provides a hint of the width of a settings column. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:Observatory.Framework.SettingDisplayName"> | ||||
|             <summary> | ||||
|             Specifies text to display as the name of the setting in the UI instead of the property name. | ||||
| @@ -20,9 +36,20 @@ | ||||
|             Accessor to get/set displayed name. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:Observatory.Framework.ColumnSuggestedWidth"> | ||||
|         <member name="T:Observatory.Framework.SettingNewGroup"> | ||||
|             <summary> | ||||
|             Suggests default column width when building basic UI | ||||
|             Starts a new visual group of settings beginning with the current setting with an optional label. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:Observatory.Framework.SettingNewGroup.#ctor(System.String)"> | ||||
|             <summary> | ||||
|             Starts a new visual group of settings beginning with the current setting with an optional label. | ||||
|             </summary> | ||||
|             <param name="label">An optional label describing the group.</param> | ||||
|         </member> | ||||
|         <member name="P:Observatory.Framework.SettingNewGroup.Label"> | ||||
|             <summary> | ||||
|             An optional label describing the group. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:Observatory.Framework.SettingIgnore"> | ||||
| @@ -56,13 +83,14 @@ | ||||
|             Specify bounds for numeric inputs. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:Observatory.Framework.SettingNumericBounds.#ctor(System.Double,System.Double,System.Double)"> | ||||
|         <member name="M:Observatory.Framework.SettingNumericBounds.#ctor(System.Double,System.Double,System.Double,System.Int32)"> | ||||
|             <summary> | ||||
|             Specify bounds for numeric inputs. | ||||
|             </summary> | ||||
|             <param name="minimum">Minimum allowed value.</param> | ||||
|             <param name="maximum">Maximum allowed value.</param> | ||||
|             <param name="increment">Increment between allowed values in slider/roller inputs.</param> | ||||
|             <param name="precision">The number of digits to display for non integer values.</param> | ||||
|         </member> | ||||
|         <member name="P:Observatory.Framework.SettingNumericBounds.Minimum"> | ||||
|             <summary> | ||||
| @@ -71,7 +99,7 @@ | ||||
|         </member> | ||||
|         <member name="P:Observatory.Framework.SettingNumericBounds.Maximum"> | ||||
|             <summary> | ||||
|             Maxunyn allowed value. | ||||
|             Maximum allowed value. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:Observatory.Framework.SettingNumericBounds.Increment"> | ||||
| @@ -79,6 +107,27 @@ | ||||
|             Increment between allowed values in slider/roller inputs. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="P:Observatory.Framework.SettingNumericBounds.Precision"> | ||||
|             <summary> | ||||
|             The number of digits to display for non integer values. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:Observatory.Framework.ColumnSuggestedWidth"> | ||||
|             <summary> | ||||
|             Suggests default column width when building basic plugin grid UI. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="M:Observatory.Framework.ColumnSuggestedWidth.#ctor(System.Int32)"> | ||||
|             <summary> | ||||
|             Suggests default column width when building basic plugin grid UI. | ||||
|             </summary> | ||||
|             <param name="width">The suggested width of the annotated column.</param> | ||||
|         </member> | ||||
|         <member name="P:Observatory.Framework.ColumnSuggestedWidth.Width"> | ||||
|             <summary> | ||||
|             The suggested width of the annotated column. | ||||
|             </summary> | ||||
|         </member> | ||||
|         <member name="T:Observatory.Framework.JournalEventArgs"> | ||||
|             <summary> | ||||
|             Provides data for Elite Dangerous journal events. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user