Skip to content

Settings

Mathieu Guindon edited this page Mar 30, 2024 · 3 revisions

Settings in RD3 have been completely overhauled, so much that the Update Server will need a tool to import and convert any relevant RD2/legacy settings.

The abstraction level has changed: rather than being expressed as properties of a given type, settings are now expressed as their own entities, and their properties are metadata that is then used for templating the settings UI.

This means all RD3 settings inherit the same base record class: RubberduckSetting, and the most-derived type to de/serialize must have a corresponding JsonDerivedTypeAttribute, because RubberduckSetting uses System.Text.Json.Serialization and the JsonPolymorphic serialization attribute. This may change in the future as it is an implementation detail.

⚠️ When adding a new setting, we must remember to add a corresponding JsonDerivedTypeAttribute to the RubberduckSetting definition.

[JsonDerivedType(typeof(NewSetting), nameof(NewSetting))]

Otherwise, adding a new setting is a very straightforward experience.

Adding a new setting

This would be the most common case: you're writing a feature, and suddenly you come across a value that could/should be configurable, so you add a //TODO make this configurable comment and think "meh, I'll get back to this later".

    // TODO make this configurable
    var isEnabled = true;

If you're "in the zone", it's probably a good idea to do this. In RD2 adding a new setting would entail crafting a UI for it, and then modifying the model all the way from the ViewModel to the serialized XML - in other words adding a new setting touches on multiple layers and can make a simple configurable feature turn into a very complex endeavor, and there goes your focus.

In RD3 however, you don't need to context-shift as much, because all you need to do is to inherit the correct record class type, and then go and add it to an appropriate existing setting group... and that's about it: you don't have to think about the settings UI at all - unless you're introducing a new data type, that is.

Figuring out which record class type to inherit would be the hard part.

Model

The RD3 settings model has lots of types, but it boils down to having a record class type for each setting data type we want to be able to represent in the settings UI, and then inheriting it whenever we need a new setting.

  • RubberduckSetting
  • TypedRubberduckSetting<TValue> : RubberduckSetting
  • BooleanRubberduckSetting : TypedRubberduckSetting<bool>
  • NumericRubberduckSetting : TypedRubberduckSetting<double>
  • StringRubberduckSetting : TypedRubberduckSetting<string>
  • UriRubberduckSetting : TypedRubberduckSetting<Uri>
  • TypedSettingGroup : TypedRubberduckSetting<RubberduckSetting[]>
  • ToolWindowSettings : TypedSettingGroup

The rest of the model is just specific types inheriting TypedSettingGroup or one of the ⭐ common/named types.

When adding a setting that needs a new parent setting group, inherit TypedSettingGroup using another existing implementation as a reference.

Conventions

  • Typed settings define a public static T DefaultSettingValue { get; } = ... property
    • Typed settings' default constructor sets DefaultValue = DefaultSettingValue
  • Setting groups define a private static readonly RubberduckSetting[] DefaultSettings = [...]; field that gets the Default value for each setting in the setting group.
    • Setting groups' default constructor sets Value = DefaultValue = DefaultSettings;
    • Setting groups implementing IDefaultSettingsProvider<T> expose a public static T Default { get; } = new() { Value = DefaultSettings, DefaultValue = DefaultSettings }; static property that is returned by an explicit implementation of IDefaultSettingsProvider<T>.Default, so T IDefaultSettingsProvider<T>.Default => Default;
    • Setting groups should expose a [JsonIgnore]-decorated public property getter that returns GetSettings<T>() ?? T.Default;
  • For now, resources are under Rubberduck.Resources.v3.SettingsUI.resx. The name of the class type serves as part of both keys that need to be added:
    • $"{SettingTypeName}_Title"
    • $"{SettingTypeName}_Description"
Clone this wiki locally