diff --git a/src/RealTime/Config/ConfigurationProvider.cs b/src/RealTime/Config/ConfigurationProvider.cs index 374fe534..b01ae258 100644 --- a/src/RealTime/Config/ConfigurationProvider.cs +++ b/src/RealTime/Config/ConfigurationProvider.cs @@ -7,80 +7,146 @@ namespace RealTime.Config using System; using System.IO; using System.Xml.Serialization; + using RealTime.Core; using RealTime.Tools; /// /// A static class that loads and stores the objects. /// An XML file 'RealTime.xml' in the default (game) directory is used as a storage. /// - internal static class ConfigurationProvider + internal sealed class ConfigurationProvider : IStorageData { + private const string StorageId = "RealTimeConfiguration"; private static readonly string SettingsFileName = typeof(ConfigurationProvider).Assembly.GetName().Name + ".xml"; + /// Occurs when the instance changes. + public event EventHandler Changed; + + /// Gets the current configuration. + public RealTimeConfig Configuration { get; private set; } + + /// Gets a value indicating whether the instance is a default configuration. + public bool IsDefault { get; private set; } + + /// Gets an unique ID of this storage data set. + string IStorageData.StorageDataId => StorageId; + /// - /// Loads the configuration object from the serialized storage. If no storage is available, + /// Loads the default configuration object from the serialized storage. If no storage is available, /// returns a new object with its values set to defaults. /// - /// - /// A object containing the configuration. - public static RealTimeConfig LoadConfiguration() + public void LoadDefaultConfiguration() { try { - if (!File.Exists(SettingsFileName)) + if (File.Exists(SettingsFileName)) { - return new RealTimeConfig(true); + using (var stream = new FileStream(SettingsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + Configuration = Deserialize(stream); + } } - - return Deserialize(); } catch (Exception ex) { - Log.Warning($"The 'Real Time' mod has encountered an error while trying to load the configuration, error message: " + ex); - return new RealTimeConfig(true); + Log.Error("The 'Real Time' mod cannot load its default configuration, error message: " + ex); } + + IsDefault = true; + if (Configuration == null) + { + Configuration = new RealTimeConfig(true); + } + + OnChanged(); } /// - /// Stores the specified object to the storage. + /// Stores the current configuration object to the storage as a default configuration. /// - /// - /// Thrown when the argument is null. - /// - /// A object to store. - public static void SaveConfiguration(RealTimeConfig config) + public void SaveDefaultConfiguration() { - if (config == null) + if (Configuration == null) { - throw new ArgumentNullException(nameof(config)); + Configuration = new RealTimeConfig(true); } try { - Serialize(config); + using (var stream = new FileStream(SettingsFileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + { + Serialize(Configuration, stream); + } } catch (Exception ex) { - Log.Error("The 'Real Time' mod cannot save its configuration, error message: " + ex); + Log.Error("The 'Real Time' mod cannot save its default configuration, error message: " + ex); } } - private static RealTimeConfig Deserialize() + /// Reads the data set from the specified . + /// A to read the data set from. + void IStorageData.ReadData(Stream source) { - var serializer = new XmlSerializer(typeof(RealTimeConfig)); - using (var sr = new StreamReader(SettingsFileName)) + Configuration = Deserialize(source); + if (Configuration == null) + { + LoadDefaultConfiguration(); + } + else { - return ((RealTimeConfig)serializer.Deserialize(sr)).MigrateWhenNecessary().Validate(); + OnChanged(); } + + IsDefault = false; } - private static void Serialize(RealTimeConfig config) + /// Stores the data set to the specified . + /// A to write the data set to. + void IStorageData.StoreData(Stream target) { - var serializer = new XmlSerializer(typeof(RealTimeConfig), SerializationTools.IgnoreObsoleteProperties(config)); - using (var sw = new StreamWriter(SettingsFileName)) + if (Configuration != null) { - serializer.Serialize(sw, config); + Serialize(Configuration, target); } } + + private static RealTimeConfig Deserialize(Stream source) + { + try + { + var serializer = new XmlSerializer(typeof(RealTimeConfig)); + using (var sr = new StreamReader(source)) + { + return ((RealTimeConfig)serializer.Deserialize(sr)).MigrateWhenNecessary().Validate(); + } + } + catch (Exception ex) + { + Log.Warning($"The 'Real Time' mod has encountered an error while trying to load the configuration, error message: " + ex); + return null; + } + } + + private static void Serialize(RealTimeConfig config, Stream target) + { + try + { + var serializer = new XmlSerializer(typeof(RealTimeConfig), SerializationTools.IgnoreObsoleteProperties(config)); + using (var sw = new StreamWriter(target)) + { + serializer.Serialize(sw, config); + } + } + catch (Exception ex) + { + Log.Error("The 'Real Time' mod cannot save its configuration, error message: " + ex); + } + } + + private void OnChanged() + { + Changed?.Invoke(this, EventArgs.Empty); + } } } diff --git a/src/RealTime/Core/RealTimeCore.cs b/src/RealTime/Core/RealTimeCore.cs index 83697432..77a4639d 100644 --- a/src/RealTime/Core/RealTimeCore.cs +++ b/src/RealTime/Core/RealTimeCore.cs @@ -45,20 +45,20 @@ private RealTimeCore(TimeAdjustment timeAdjustment, CustomTimeBar timeBar, RealT /// Runs the mod by activating its parts. /// /// - /// Thrown when or is null. + /// Thrown when or is null. /// Thrown when is null or an empty string. /// - /// The configuration to run with. + /// The configuration provider that provides the mod's configuration. /// The path to the mod's assembly. Additional files are stored here too. /// The to use for text translation. /// /// A instance that can be used to stop the mod. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This is the entry point and needs to instantiate all parts")] - public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocalizationProvider localizationProvider) + public static RealTimeCore Run(ConfigurationProvider configProvider, string rootPath, ILocalizationProvider localizationProvider) { - if (config == null) + if (configProvider == null) { - throw new ArgumentNullException(nameof(config)); + throw new ArgumentNullException(nameof(configProvider)); } if (string.IsNullOrEmpty(rootPath)) @@ -96,7 +96,12 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocaliza return null; } - var timeInfo = new TimeInfo(config); + if (RealTimeStorage.CurrentLevelStorage != null) + { + LoadStorageData(new[] { configProvider }, RealTimeStorage.CurrentLevelStorage); + } + + var timeInfo = new TimeInfo(configProvider.Configuration); var buildingManager = new BuildingManagerConnection(); var randomizer = new GameRandomizer(); @@ -112,21 +117,21 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocaliza weatherInfo); var eventManager = new RealTimeEventManager( - config, + configProvider.Configuration, CityEventsLoader.Instance, new EventManagerConnection(), buildingManager, randomizer, timeInfo); - if (!SetupCustomAI(timeInfo, config, gameConnections, eventManager)) + if (!SetupCustomAI(timeInfo, configProvider.Configuration, gameConnections, eventManager)) { Log.Error("The 'Real Time' mod failed to setup the customized AI and will now be deactivated."); patcher.Revert(); return null; } - var timeAdjustment = new TimeAdjustment(config); + var timeAdjustment = new TimeAdjustment(configProvider.Configuration); DateTime gameDate = timeAdjustment.Enable(); SimulationHandler.CitizenProcessor.UpdateFrameDuration(); @@ -141,19 +146,24 @@ public static RealTimeCore Run(RealTimeConfig config, string rootPath, ILocaliza SimulationHandler.NewDay += result.CityEventsChanged; SimulationHandler.TimeAdjustment = timeAdjustment; - SimulationHandler.DayTimeSimulation = new DayTimeSimulation(config); + SimulationHandler.DayTimeSimulation = new DayTimeSimulation(configProvider.Configuration); SimulationHandler.EventManager = eventManager; SimulationHandler.WeatherInfo = weatherInfo; SimulationHandler.Buildings = BuildingAIPatches.RealTimeAI; SimulationHandler.Buildings.UpdateFrameDuration(); SimulationHandler.Buildings.InitializeLightState(); - AwakeSleepSimulation.Install(config); + AwakeSleepSimulation.Install(configProvider.Configuration); RealTimeStorage.CurrentLevelStorage.GameSaving += result.GameSaving; result.storageData.Add(eventManager); result.storageData.Add(ResidentAIPatch.RealTimeAI.GetStorageService()); - result.LoadStorageData(RealTimeStorage.CurrentLevelStorage); + if (RealTimeStorage.CurrentLevelStorage != null) + { + LoadStorageData(result.storageData, RealTimeStorage.CurrentLevelStorage); + } + + result.storageData.Add(configProvider); result.Translate(localizationProvider); @@ -278,19 +288,18 @@ private static void CustomTimeBarCityEventClick(object sender, CustomTimeBarClic CameraHelper.NavigateToBuilding(e.CityEventBuildingId); } - private void CityEventsChanged(object sender, EventArgs e) - { - timeBar.UpdateEventsDisplay(eventManager.CityEvents); - } - - private void LoadStorageData(RealTimeStorage storage) + private static void LoadStorageData(IEnumerable storageData, RealTimeStorage storage) { foreach (IStorageData item in storageData) { storage.Deserialize(item); + Log.Debug("The 'Real Time' mod loaded its data from container " + item.StorageDataId); } + } - Log.Info("The 'Real Time' mod successfully loaded its data for the current game."); + private void CityEventsChanged(object sender, EventArgs e) + { + timeBar.UpdateEventsDisplay(eventManager.CityEvents); } private void GameSaving(object sender, EventArgs e) @@ -299,9 +308,8 @@ private void GameSaving(object sender, EventArgs e) foreach (IStorageData item in storageData) { storage.Serialize(item); + Log.Debug("The 'Real Time' mod stored its data in the current game for container " + item.StorageDataId); } - - Log.Info("The 'Real Time' mod successfully stored its data in the current game."); } } } diff --git a/src/RealTime/Core/RealTimeMod.cs b/src/RealTime/Core/RealTimeMod.cs index 467e862c..642d2c0c 100644 --- a/src/RealTime/Core/RealTimeMod.cs +++ b/src/RealTime/Core/RealTimeMod.cs @@ -25,7 +25,7 @@ public sealed class RealTimeMod : LoadingExtensionBase, IUserMod private readonly string modVersion = GitVersion.GetAssemblyVersion(typeof(RealTimeMod).Assembly); private readonly string modPath = GetModPath(); - private RealTimeConfig config; + private ConfigurationProvider configProvider; private RealTimeCore core; private ConfigUI configUI; private LocalizationProvider localizationProvider; @@ -46,7 +46,8 @@ public sealed class RealTimeMod : LoadingExtensionBase, IUserMod public void OnEnabled() { Log.Info("The 'Real Time' mod has been enabled, version: " + modVersion); - config = ConfigurationProvider.LoadConfiguration(); + configProvider = new ConfigurationProvider(); + configProvider.LoadDefaultConfiguration(); localizationProvider = new LocalizationProvider(modPath); } @@ -57,9 +58,13 @@ public void OnEnabled() public void OnDisabled() { Log.Info("The 'Real Time' mod has been disabled."); - ConfigurationProvider.SaveConfiguration(config); - config = null; - configUI = null; + + CloseConfigUI(); + + if (configProvider != null && configProvider.IsDefault) + { + configProvider.SaveDefaultConfiguration(); + } } /// @@ -71,19 +76,20 @@ public void OnDisabled() [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Must be instance method due to C:S API")] public void OnSettingsUI(UIHelperBase helper) { - if (helper == null) + if (helper == null || configProvider == null) { return; } - if (config == null) + if (configProvider.Configuration == null) { Log.Warning("The 'Real Time' mod wants to display the configuration page, but the configuration is unexpectedly missing."); - config = ConfigurationProvider.LoadConfiguration(); + configProvider.LoadDefaultConfiguration(); } IViewItemFactory itemFactory = new CitiesViewItemFactory(helper); - configUI = ConfigUI.Create(config, itemFactory); + CloseConfigUI(); + configUI = ConfigUI.Create(configProvider, itemFactory); ApplyLanguage(); } @@ -113,7 +119,7 @@ public override void OnLevelLoaded(LoadMode mode) core.Stop(); } - core = RealTimeCore.Run(config, modPath, localizationProvider); + core = RealTimeCore.Run(configProvider, modPath, localizationProvider); if (core == null) { Log.Warning("Showing a warning message to user because the mod isn't working"); @@ -136,7 +142,8 @@ public override void OnLevelUnloading() core = null; } - ConfigurationProvider.SaveConfiguration(config); + configProvider.LoadDefaultConfiguration(); + CloseConfigUI(); } private static string GetModPath() @@ -165,5 +172,14 @@ private void ApplyLanguage() configUI?.Translate(localizationProvider); } + + private void CloseConfigUI() + { + if (configUI != null) + { + configUI.Close(); + configUI = null; + } + } } } diff --git a/src/RealTime/Localization/Translations/de.xml b/src/RealTime/Localization/Translations/de.xml index d3985ab9..1801075b 100644 --- a/src/RealTime/Localization/Translations/de.xml +++ b/src/RealTime/Localization/Translations/de.xml @@ -6,6 +6,7 @@ + diff --git a/src/RealTime/Localization/Translations/en.xml b/src/RealTime/Localization/Translations/en.xml index 5bc38002..bf6c4127 100644 --- a/src/RealTime/Localization/Translations/en.xml +++ b/src/RealTime/Localization/Translations/en.xml @@ -6,6 +6,7 @@ + diff --git a/src/RealTime/Localization/Translations/es.xml b/src/RealTime/Localization/Translations/es.xml index dad548c1..cece0a9e 100644 --- a/src/RealTime/Localization/Translations/es.xml +++ b/src/RealTime/Localization/Translations/es.xml @@ -6,6 +6,7 @@ + diff --git a/src/RealTime/Localization/Translations/fr.xml b/src/RealTime/Localization/Translations/fr.xml index 1863d84a..a1313571 100644 --- a/src/RealTime/Localization/Translations/fr.xml +++ b/src/RealTime/Localization/Translations/fr.xml @@ -6,6 +6,7 @@ + diff --git a/src/RealTime/Localization/Translations/ko.xml b/src/RealTime/Localization/Translations/ko.xml index 3a28fbd4..507eca96 100644 --- a/src/RealTime/Localization/Translations/ko.xml +++ b/src/RealTime/Localization/Translations/ko.xml @@ -6,6 +6,7 @@ + diff --git a/src/RealTime/Localization/Translations/pl.xml b/src/RealTime/Localization/Translations/pl.xml index 55ad00ea..c9d10307 100644 --- a/src/RealTime/Localization/Translations/pl.xml +++ b/src/RealTime/Localization/Translations/pl.xml @@ -6,6 +6,7 @@ + diff --git a/src/RealTime/Localization/Translations/pt.xml b/src/RealTime/Localization/Translations/pt.xml index c2021afd..289a7588 100644 --- a/src/RealTime/Localization/Translations/pt.xml +++ b/src/RealTime/Localization/Translations/pt.xml @@ -6,6 +6,7 @@ + diff --git a/src/RealTime/Localization/Translations/ru.xml b/src/RealTime/Localization/Translations/ru.xml index 3e8d97f2..d0493386 100644 --- a/src/RealTime/Localization/Translations/ru.xml +++ b/src/RealTime/Localization/Translations/ru.xml @@ -6,6 +6,7 @@ + diff --git a/src/RealTime/Localization/Translations/zh.xml b/src/RealTime/Localization/Translations/zh.xml index 51242026..30265ba6 100644 --- a/src/RealTime/Localization/Translations/zh.xml +++ b/src/RealTime/Localization/Translations/zh.xml @@ -6,6 +6,7 @@ + diff --git a/src/RealTime/UI/CitiesCheckBoxItem.cs b/src/RealTime/UI/CitiesCheckBoxItem.cs index 55a22270..2197a5a6 100644 --- a/src/RealTime/UI/CitiesCheckBoxItem.cs +++ b/src/RealTime/UI/CitiesCheckBoxItem.cs @@ -4,6 +4,7 @@ namespace RealTime.UI { + using System; using System.Reflection; using ColossalFramework.UI; using ICities; @@ -18,13 +19,13 @@ internal sealed class CitiesCheckBoxItem : CitiesViewItem /// /// The property description that specifies the target property where to store the value. /// - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// Thrown when any argument is null. /// /// thrown when the is an empty string. /// - public CitiesCheckBoxItem(UIHelperBase uiHelper, string id, PropertyInfo property, object config) - : base(uiHelper, id, property, config) + public CitiesCheckBoxItem(UIHelperBase uiHelper, string id, PropertyInfo property, Func configProvider) + : base(uiHelper, id, property, configProvider) { } diff --git a/src/RealTime/UI/CitiesComboBoxItem.cs b/src/RealTime/UI/CitiesComboBoxItem.cs index d9f8777d..6de2a70b 100644 --- a/src/RealTime/UI/CitiesComboBoxItem.cs +++ b/src/RealTime/UI/CitiesComboBoxItem.cs @@ -25,14 +25,14 @@ internal sealed class CitiesComboBoxItem : CitiesViewItem /// /// The property description that specifies the target property where to store the value. /// - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// An ordered collection of the combo box item IDs. /// Thrown when any argument is null. /// /// thrown when the is an empty string. /// - public CitiesComboBoxItem(UIHelperBase uiHelper, string id, PropertyInfo property, object config, IEnumerable itemIds) - : base(uiHelper, id, property, config) + public CitiesComboBoxItem(UIHelperBase uiHelper, string id, PropertyInfo property, Func configProvider, IEnumerable itemIds) + : base(uiHelper, id, property, configProvider) { this.itemIds = itemIds ?? throw new ArgumentNullException(nameof(itemIds)); diff --git a/src/RealTime/UI/CitiesSliderItem.cs b/src/RealTime/UI/CitiesSliderItem.cs index 6de687aa..afeb4458 100644 --- a/src/RealTime/UI/CitiesSliderItem.cs +++ b/src/RealTime/UI/CitiesSliderItem.cs @@ -28,7 +28,7 @@ internal sealed class CitiesSliderItem : CitiesViewItem /// /// The property description that specifies the target property where to store the value. /// - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// The minimum slider value. /// The maximum slider value. /// The slider step value. Default is 1. @@ -42,13 +42,13 @@ public CitiesSliderItem( UIHelperBase uiHelper, string id, PropertyInfo property, - object config, + Func configProvider, float min, float max, float step, SliderValueType valueType, float displayMultiplier) - : base(uiHelper, id, property, config) + : base(uiHelper, id, property, configProvider) { UIComponent.minValue = min; UIComponent.maxValue = max; @@ -69,6 +69,8 @@ public CitiesSliderItem( valueLabel.name = id + LabelName; UpdateValueLabel(Value); } + + Refresh(); } /// Translates this view item using the specified localization provider. diff --git a/src/RealTime/UI/CitiesViewItem.cs b/src/RealTime/UI/CitiesViewItem.cs index 9916b406..c395e521 100644 --- a/src/RealTime/UI/CitiesViewItem.cs +++ b/src/RealTime/UI/CitiesViewItem.cs @@ -18,7 +18,7 @@ internal abstract class CitiesViewItem : IViewItem, IValueViewIte where TItem : UIComponent { private readonly PropertyInfo property; - private readonly object config; + private readonly Func configProvider; /// /// Initializes a new instance of the class. @@ -28,13 +28,13 @@ internal abstract class CitiesViewItem : IViewItem, IValueViewIte /// /// The property description that specifies the target property where to store the value. /// - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// Thrown when any argument is null. /// /// thrown when the is an empty string. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Pure methods invoked")] - protected CitiesViewItem(UIHelperBase uiHelper, string id, PropertyInfo property, object config) + protected CitiesViewItem(UIHelperBase uiHelper, string id, PropertyInfo property, Func configProvider) { if (uiHelper == null) { @@ -42,7 +42,7 @@ protected CitiesViewItem(UIHelperBase uiHelper, string id, PropertyInfo property } this.property = property ?? throw new ArgumentNullException(nameof(property)); - this.config = config ?? throw new ArgumentNullException(nameof(config)); + this.configProvider = configProvider ?? throw new ArgumentNullException(nameof(configProvider)); if (id == null) { @@ -64,13 +64,13 @@ protected CitiesViewItem(UIHelperBase uiHelper, string id, PropertyInfo property /// Gets current configuration item value. protected TValue Value { - get => (TValue)Convert.ChangeType(property.GetValue(config, null), typeof(TValue)); + get => (TValue)Convert.ChangeType(property.GetValue(configProvider(), null), typeof(TValue)); private set { object newValue = property.PropertyType.IsEnum ? Enum.ToObject(property.PropertyType, value) : Convert.ChangeType(value, property.PropertyType); - property.SetValue(config, newValue, null); + property.SetValue(configProvider(), newValue, null); } } diff --git a/src/RealTime/UI/CitiesViewItemFactory.cs b/src/RealTime/UI/CitiesViewItemFactory.cs index 24e443ef..ea261ff6 100644 --- a/src/RealTime/UI/CitiesViewItemFactory.cs +++ b/src/RealTime/UI/CitiesViewItemFactory.cs @@ -101,20 +101,20 @@ public IViewItem CreateButton(IContainerViewItem container, string id, Action cl /// The parent container for the created item. /// The ID of the item to create. /// The property description that specifies the target property where to store the value. - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// A newly created instance representing a check box. /// Thrown when any argument is null. /// Thrown when is an empty string. - public IViewItem CreateCheckBox(IContainerViewItem container, string id, PropertyInfo property, object config) + public IViewItem CreateCheckBox(IContainerViewItem container, string id, PropertyInfo property, Func configProvider) { - return new CitiesCheckBoxItem(container.Container, id, property, config); + return new CitiesCheckBoxItem(container.Container, id, property, configProvider); } /// Creates a new slider view item. /// The parent container for the created item. /// The ID of the item to create. /// The property description that specifies the target property where to store the value. - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// The minimum slider value. /// The maximum slider value. /// The slider step value. @@ -133,21 +133,21 @@ public IViewItem CreateSlider( IContainerViewItem container, string id, PropertyInfo property, - object config, + Func configProvider, float min, float max, float step, SliderValueType valueType, float displayMultiplier) { - return new CitiesSliderItem(container.Container, id, property, config, min, max, step, valueType, displayMultiplier); + return new CitiesSliderItem(container.Container, id, property, configProvider, min, max, step, valueType, displayMultiplier); } /// Creates a new combo box view item. /// The parent container for the created item. /// The ID of the item to create. /// The property description that specifies the target property where to store the value. - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// A collection of the item IDs for the combo box values. /// A newly created instance representing a combo box. /// Thrown when any argument is null. @@ -156,10 +156,10 @@ public IViewItem CreateComboBox( IContainerViewItem container, string id, PropertyInfo property, - object config, + Func configProvider, IEnumerable itemIds) { - return new CitiesComboBoxItem(container.Container, id, property, config, itemIds); + return new CitiesComboBoxItem(container.Container, id, property, configProvider, itemIds); } } } \ No newline at end of file diff --git a/src/RealTime/UI/ConfigUI.cs b/src/RealTime/UI/ConfigUI.cs index 47521883..24a2ac2c 100644 --- a/src/RealTime/UI/ConfigUI.cs +++ b/src/RealTime/UI/ConfigUI.cs @@ -15,29 +15,33 @@ namespace RealTime.UI internal sealed class ConfigUI { private const string ResetToDefaultsId = "ResetToDefaults"; + private const string UseForNewGamesId = "UseForNewGames"; private const string ToolsId = "Tools"; - private readonly RealTimeConfig config; + private readonly ConfigurationProvider configProvider; private readonly IEnumerable viewItems; - private ConfigUI(RealTimeConfig config, IEnumerable viewItems) + private ConfigUI(ConfigurationProvider configProvider, IEnumerable viewItems) { - this.config = config; + this.configProvider = configProvider; this.viewItems = viewItems; + this.configProvider.Changed += ConfigProviderChanged; } /// /// Creates the mod's configuration page using the specified object as data source. /// - /// The configuration object to use as data source. + /// The mod's configuration provider. /// The view item factory to use for creating the UI elements. /// A configured instance of the class. /// Thrown when any argument is null. - public static ConfigUI Create(RealTimeConfig config, IViewItemFactory itemFactory) + /// Thrown when the specified + /// is not initialized yet. + public static ConfigUI Create(ConfigurationProvider configProvider, IViewItemFactory itemFactory) { - if (config == null) + if (configProvider == null) { - throw new ArgumentNullException(nameof(config)); + throw new ArgumentNullException(nameof(configProvider)); } if (itemFactory == null) @@ -45,7 +49,12 @@ public static ConfigUI Create(RealTimeConfig config, IViewItemFactory itemFactor throw new ArgumentNullException(nameof(itemFactory)); } - var properties = config.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) + if (configProvider.Configuration == null) + { + throw new InvalidOperationException("The configuration provider has no configuration yet"); + } + + var properties = configProvider.Configuration.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(p => new { Property = p, Attribute = GetCustomItemAttribute(p) }) .Where(v => v.Attribute != null); @@ -71,7 +80,7 @@ public static ConfigUI Create(RealTimeConfig config, IViewItemFactory itemFactor foreach (var item in group.OrderBy(i => i.Attribute.Order)) { - IViewItem viewItem = CreateViewItem(containerItem, item.Property, config, itemFactory); + IViewItem viewItem = CreateViewItem(containerItem, item.Property, configProvider, itemFactory); if (viewItem != null) { viewItems.Add(viewItem); @@ -80,16 +89,25 @@ public static ConfigUI Create(RealTimeConfig config, IViewItemFactory itemFactor } } - var result = new ConfigUI(config, viewItems); + var result = new ConfigUI(configProvider, viewItems); IContainerViewItem toolsTab = itemFactory.CreateTabItem(ToolsId); viewItems.Add(toolsTab); + IViewItem resetButton = itemFactory.CreateButton(toolsTab, ResetToDefaultsId, result.ResetToDefaults); viewItems.Add(resetButton); + IViewItem newGameConfigButton = itemFactory.CreateButton(toolsTab, UseForNewGamesId, result.UseForNewGames); + viewItems.Add(newGameConfigButton); return result; } + /// Closes this instance. + public void Close() + { + configProvider.Changed -= ConfigProviderChanged; + } + /// Translates the UI using the specified localization provider. /// The localization provider to use for translation. public void Translate(ILocalizationProvider localizationProvider) @@ -100,8 +118,14 @@ public void Translate(ILocalizationProvider localizationProvider) } } - private static IViewItem CreateViewItem(IContainerViewItem container, PropertyInfo property, object config, IViewItemFactory itemFactory) + private static IViewItem CreateViewItem( + IContainerViewItem container, + PropertyInfo property, + ConfigurationProvider configProvider, + IViewItemFactory itemFactory) { + object config() => configProvider.Configuration; + switch (GetCustomItemAttribute(property)) { case ConfigItemSliderAttribute slider when property.PropertyType.IsPrimitive: @@ -141,7 +165,22 @@ private static T GetCustomItemAttribute(PropertyInfo property, bool inherit = private void ResetToDefaults() { - config.ResetToDefaults(); + configProvider.Configuration.ResetToDefaults(); + RefreshAllItems(); + } + + private void UseForNewGames() + { + configProvider.SaveDefaultConfiguration(); + } + + private void ConfigProviderChanged(object sender, EventArgs e) + { + RefreshAllItems(); + } + + private void RefreshAllItems() + { foreach (IValueViewItem item in viewItems.OfType()) { item.Refresh(); diff --git a/src/RealTime/UI/IViewItemFactory.cs b/src/RealTime/UI/IViewItemFactory.cs index 82b365bb..49d1a3a8 100644 --- a/src/RealTime/UI/IViewItemFactory.cs +++ b/src/RealTime/UI/IViewItemFactory.cs @@ -36,17 +36,17 @@ internal interface IViewItemFactory /// The parent container for the created item. /// The ID of the item to create. /// The property description that specifies the target property where to store the value. - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// A newly created instance representing a check box. /// Thrown when any argument is null. /// Thrown when is an empty string. - IViewItem CreateCheckBox(IContainerViewItem container, string id, PropertyInfo property, object config); + IViewItem CreateCheckBox(IContainerViewItem container, string id, PropertyInfo property, Func configProvider); /// Creates a new slider view item. /// The parent container for the created item. /// The ID of the item to create. /// The property description that specifies the target property where to store the value. - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// The minimum slider value. /// The maximum slider value. /// The slider step value. @@ -65,7 +65,7 @@ IViewItem CreateSlider( IContainerViewItem container, string id, PropertyInfo property, - object config, + Func configProvider, float min, float max, float step, @@ -76,7 +76,7 @@ IViewItem CreateSlider( /// The parent container for the created item. /// The ID of the item to create. /// The property description that specifies the target property where to store the value. - /// The configuration storage object for the value. + /// A method that provides the configuration storage object for the value. /// A collection of the item IDs for the combo box values. /// A newly created instance representing a combo box. /// Thrown when any argument is null. @@ -85,7 +85,7 @@ IViewItem CreateComboBox( IContainerViewItem container, string id, PropertyInfo property, - object config, + Func configProvider, IEnumerable itemIds); } } \ No newline at end of file