/// <summary> /// Sets the alignment of this component. You should not call this yourself, and this must not be called after the component has been built. /// </summary> public void SetAlignment(GadgetConfigComponentAlignment alignment) { if (ConfigMenu.HasBuilt) { throw new InvalidOperationException("A GadgetConfigComponent's alignment cannot be set after it has been built!"); } Alignment = alignment; }
/// <summary> /// Adds a <see cref="GadgetConfigComponent"/> to this config menu. May only be called before the menu is built. /// </summary> public virtual void AddComponent(GadgetConfigComponent component, GadgetConfigComponentAlignment alignment = GadgetConfigComponentAlignment.STANDARD) { if (HasBuilt) { throw new InvalidOperationException("GadgetConfigComponents may not be added to a BasicGadgetConfigMenu after the menu has been built!"); } component.SetAlignment(alignment); ConfigComponents.Add(component); }
/// <summary> /// Loads the given config file, and adds all of the entries within to this <see cref="INIGadgetConfigMenu"/> /// </summary> protected virtual void LoadConfigFile(string configFilePath, string section, params string[] readonlyEntries) { ConfigFilePath = configFilePath; ConfigFileSection = section; ReadonlyEntries = readonlyEntries; if (!File.Exists(configFilePath)) { GadgetCore.CoreLogger.LogConsole(configFilePath); AddComponent(new GadgetConfigLabelComponent(this, "No Config File", "This mod had no config file when this menu was generated.")); AddComponent(new GadgetConfigLabelComponent(this, "No Config File", "Perhaps it was just installed?")); AddComponent(new GadgetConfigLabelComponent(this, "No Config File", "Click the button to reload this config menu.")); AddComponent(new GadgetConfigSpacerComponent(this, "Spacer")); AddComponent(new GadgetConfigButtonComponent(this, null, "Refresh Config File", () => { Reset(); }, 0.25f)); return; } IniParser = new FileIniDataParser(); Ini = IniParser.ReadFile(configFilePath); Ini.Configuration.SkipInvalidLines = true; Ini.Configuration.AssigmentSpacer = ""; if (section == null || !Ini.Sections.ContainsSection(section)) { section = Path.GetFileNameWithoutExtension(configFilePath); if (!Ini.Sections.ContainsSection(section)) { throw new InvalidOperationException(NO_CONFIGURABLE_DATA); } } ConfigFileSection = section; if (Ini.Sections.ContainsSection("uModFramework") && Ini["uModFramework"].ContainsKey("ConfigVersion")) { AddComponent(new GadgetConfigLabelComponent(this, "ConfigVersion", "Config Version (not to be confused with mod version): " + Ini["uModFramework"]["ConfigVersion"], allowHeightResize: true), GadgetConfigComponentAlignment.HEADER); } bool firstStandard = true, firstHeader = false, firstFooter = true; foreach (KeyData keyData in Ini[section]) { if (keyData == null) { continue; } if (keyData.KeyName == "ConfigVersion") { AddComponent(new GadgetConfigLabelComponent(this, "ConfigVersion", "Config Version (not to be confused with mod version): " + keyData.Value, allowHeightResize: true), GadgetConfigComponentAlignment.HEADER); continue; } GadgetConfigComponentAlignment alignment = default; bool seperatorMade = false, commentsMade = false; try { string dataTypeString = keyData.Comments.SingleOrDefault((x) => x.StartsWith("[Type:")); string[] dataTypeStrings; string dataRangeString = null; if (!string.IsNullOrEmpty(dataTypeString)) { dataTypeStrings = dataTypeString.Split(new char[] { '|' }, 2); dataTypeString = dataTypeStrings[0]; if (dataTypeStrings.Length == 2 && dataTypeStrings[1].StartsWith(" Range: ")) { dataRangeString = dataTypeStrings[1].Substring(8, dataTypeStrings[1].Length - 9); } dataTypeString = dataTypeString.Substring(7, dataTypeString.Length - 8); } else { dataTypeString = "None"; } string defaultValueString = keyData.Comments.SingleOrDefault((x) => x.StartsWith("[Default(s):")); string vanillaValueString = keyData.Comments.SingleOrDefault((x) => x.StartsWith("[Vanilla:")); if (!string.IsNullOrEmpty(defaultValueString) && defaultValueString != "[Default(s): n/a]") { string[] defaultValueStrings = defaultValueString.Split('|'); defaultValueString = defaultValueStrings[0]; if (defaultValueStrings.Length > 1) { vanillaValueString = defaultValueStrings[1].Substring(10, defaultValueStrings[1].Length - 11); } defaultValueString = defaultValueString.Substring(13, defaultValueString.Length - 14); } else if (!string.IsNullOrEmpty(vanillaValueString) && vanillaValueString != "[Vanilla: n/a]") { vanillaValueString = vanillaValueString.Substring(10, vanillaValueString.Length - 11); } else { defaultValueString = null; } string allowedValuesString = keyData.Comments.SingleOrDefault((x) => x.StartsWith("[Allowed:")); if (!string.IsNullOrEmpty(allowedValuesString)) { allowedValuesString = allowedValuesString.Substring(10, allowedValuesString.Length - 11); } else { allowedValuesString = null; } string requiresRestartString = keyData.Comments.SingleOrDefault((x) => x.StartsWith("[Restart:")); if (!string.IsNullOrEmpty(requiresRestartString)) { requiresRestartString = requiresRestartString.Substring(10, requiresRestartString.Length - 11); } else { requiresRestartString = null; } bool requiresRestart = requiresRestartString != null && bool.Parse(requiresRestartString); GadgetConfigComponentAlignment?nullableAlignment = AlignConfigEntry(keyData.KeyName); if (nullableAlignment == null) { continue; } alignment = (GadgetConfigComponentAlignment)nullableAlignment; if ((alignment == GadgetConfigComponentAlignment.STANDARD && firstStandard == true) || (alignment == GadgetConfigComponentAlignment.HEADER && firstHeader == true) || (alignment == GadgetConfigComponentAlignment.FOOTER && firstFooter == true)) { switch (alignment) { case GadgetConfigComponentAlignment.STANDARD: firstStandard = false; break; case GadgetConfigComponentAlignment.HEADER: firstHeader = false; break; case GadgetConfigComponentAlignment.FOOTER: firstFooter = false; break; } } else { AddComponent(new GadgetConfigSeparatorComponent(this, "Seperator"), alignment); } seperatorMade = true; foreach (string comment in keyData.Comments) { if (!string.IsNullOrEmpty(comment) && comment[0] != '[') { AddComponent(new GadgetConfigLabelComponent(this, "Comment For: " + keyData.KeyName, comment, 0.05f, true), alignment); } } commentsMade = true; dataTypeStrings = dataTypeString.Split(new char[] { '-' }, 2); switch (dataTypeStrings[0]) { case "Boolean": bool boolValue = bool.Parse(keyData.Value); bool?boolDefaultValue = defaultValueString != null ? (bool?)bool.Parse(defaultValueString) : null; bool?boolVanillaValue = vanillaValueString != null ? (bool?)bool.Parse(vanillaValueString) : null; AddComponent(new GadgetConfigBoolComponent(this, keyData.KeyName, boolValue, (b) => SetConfigValue(section, keyData.KeyName, b.ToString()), readonlyEntries.Contains(keyData.KeyName), boolDefaultValue, boolVanillaValue), alignment); break; case "Int32": int intValue = int.Parse(keyData.Value); int[] intRange = dataRangeString?.Split(',').Select(x => int.Parse(x)).ToArray(); if (intRange != null && intRange.Length != 2) { throw new InvalidDataException("The range '" + dataRangeString + "' is not valid!"); } int?intDefaultValue = defaultValueString != null ? (int?)int.Parse(defaultValueString) : null; int?intVanillaValue = vanillaValueString != null ? (int?)int.Parse(vanillaValueString) : null; AddComponent(new GadgetConfigIntComponent(this, keyData.KeyName, intValue, (s) => SetConfigValue(section, keyData.KeyName, s.ToString()), intRange != null ? intRange[0] : 0, intRange != null ? intRange[1] : 0, readonlyEntries.Contains(keyData.KeyName), intDefaultValue, intVanillaValue), alignment); break; case "Single": float floatValue = float.Parse(keyData.Value); float[] floatRange = dataRangeString?.Split(',').Select(x => float.Parse(x)).ToArray(); if (floatRange != null && floatRange.Length != 2 && floatRange.Length != 3) { throw new InvalidDataException("The range '" + dataRangeString + "' is not valid!"); } float?floatDefaultValue = defaultValueString != null ? (float?)float.Parse(defaultValueString) : null; float?floatVanillaValue = vanillaValueString != null ? (float?)float.Parse(vanillaValueString) : null; AddComponent(new GadgetConfigFloatComponent(this, keyData.KeyName, floatValue, (s) => SetConfigValue(section, keyData.KeyName, s.ToString()), floatRange != null ? floatRange[0] : 0, floatRange != null ? floatRange[1] : 0, floatRange != null && floatRange.Length == 3 ? (int)floatRange[2] : -1, readonlyEntries.Contains(keyData.KeyName), floatDefaultValue, floatVanillaValue), alignment); break; case "Double": double doubleValue = double.Parse(keyData.Value); double[] doubleRange = dataRangeString?.Split(',').Select(x => double.Parse(x)).ToArray(); if (doubleRange != null && doubleRange.Length != 2 && doubleRange.Length != 3) { throw new InvalidDataException("The range '" + dataRangeString + "' is not valid!"); } double?doubleDefaultValue = defaultValueString != null ? (double?)double.Parse(defaultValueString) : null; double?doubleVanillaValue = vanillaValueString != null ? (double?)double.Parse(vanillaValueString) : null; AddComponent(new GadgetConfigDoubleComponent(this, keyData.KeyName, doubleValue, (s) => SetConfigValue(section, keyData.KeyName, s.ToString()), doubleRange != null ? doubleRange[0] : 0, doubleRange != null ? doubleRange[1] : 0, doubleRange != null && doubleRange.Length == 3 ? (int)doubleRange[2] : -1, readonlyEntries.Contains(keyData.KeyName), doubleDefaultValue, doubleVanillaValue), alignment); break; case "String": string stringValue = keyData.Value; string stringDefaultValue = defaultValueString; string stringVanillaValue = vanillaValueString; if (keyData.Comments.Contains("[IsKeyBind: True]")) { AddComponent(new GadgetConfigKeybindComponent(this, keyData.KeyName, stringValue, (s) => SetConfigValue(section, keyData.KeyName, s), true, readonlyEntries.Contains(keyData.KeyName), stringDefaultValue, stringVanillaValue), alignment); } else { AddComponent(new GadgetConfigStringComponent(this, keyData.KeyName, stringValue, (s) => SetConfigValue(section, keyData.KeyName, s), readonlyEntries.Contains(keyData.KeyName), stringDefaultValue, stringVanillaValue), alignment); } break; case "String[]": string[] stringArrayValue = keyData.Value.Split(','); string[] stringArrayDefaultValue = defaultValueString?.Split(','); string[] stringArrayVanillaValue = vanillaValueString?.Split(','); if (keyData.Comments.Contains("[IsKeyBind: True]")) { AddComponent(new GadgetConfigMultiKeybindComponent(this, keyData.KeyName, stringArrayValue, (s) => SetConfigValue(section, keyData.KeyName, s?.Aggregate(new StringBuilder(), (x, y) => { if (x.Length > 0) { x.Append(','); } x.Append(y); return(x); })?.ToString()), true, readonlyEntries.Contains(keyData.KeyName), stringArrayDefaultValue, stringArrayVanillaValue), alignment); } else { AddComponent(new GadgetConfigMultiStringComponent(this, keyData.KeyName, stringArrayValue, (s) => SetConfigValue(section, keyData.KeyName, s?.Aggregate(new StringBuilder(), (x, y) => { if (x.Length > 0) { x.Append(','); } x.Append(y); return(x); })?.ToString()), readonlyEntries.Contains(keyData.KeyName), stringArrayDefaultValue, stringArrayVanillaValue), alignment); } break; case "KeyCode": string keyCodeValue = keyData.Value; string keyCodeDefaultValue = defaultValueString; string keyCodeVanillaValue = vanillaValueString; AddComponent(new GadgetConfigKeybindComponent(this, keyData.KeyName, keyCodeValue, (s) => SetConfigValue(section, keyData.KeyName, s), false, readonlyEntries.Contains(keyData.KeyName), keyCodeDefaultValue, keyCodeVanillaValue), alignment); break; case "Enum": Type enumType; try { if (dataTypeStrings.Length == 2 && (enumType = Type.GetType(dataTypeStrings[1], false)) != null) { string enumValue = keyData.Value; string enumDefaultValue = defaultValueString; string enumVanillaValue = vanillaValueString; AddComponent(new GadgetConfigDropdownComponent(this, keyData.KeyName, Regex.Replace(enumValue, PASCAL_CASE_SPACING_REGEX, " $1"), Enum.GetNames(enumType).Select(x => Regex.Replace(x, PASCAL_CASE_SPACING_REGEX, " $1")).ToArray(), (s) => SetConfigValue(section, keyData.KeyName, s.Replace(" ", "")), readonlyEntries.Contains(keyData.KeyName), enumDefaultValue, enumVanillaValue), alignment); break; } } catch (Exception) { } goto default; default: string unknownValue = keyData.Value; string unknownDefaultValue = defaultValueString; string unknownVanillaValue = vanillaValueString; AddComponent(new GadgetConfigStringComponent(this, keyData.KeyName, unknownValue, (s) => SetConfigValue(section, keyData.KeyName, s), readonlyEntries.Contains(keyData.KeyName), unknownDefaultValue, unknownVanillaValue), alignment); break; } } catch (Exception e) { GadgetCore.CoreLogger.Log("WARNING: Exception parsing config entry '" + keyData.KeyName + "' with value '" + keyData.Value + "': " + e); try { if (!seperatorMade) { GadgetConfigComponentAlignment?nullableAlignment = AlignConfigEntry(keyData.KeyName); if (nullableAlignment == null) { continue; } alignment = (GadgetConfigComponentAlignment)nullableAlignment; if ((alignment == GadgetConfigComponentAlignment.STANDARD && firstStandard == true) || (alignment == GadgetConfigComponentAlignment.HEADER && firstHeader == true) || (alignment == GadgetConfigComponentAlignment.FOOTER && firstFooter == true)) { switch (alignment) { case GadgetConfigComponentAlignment.STANDARD: firstStandard = false; break; case GadgetConfigComponentAlignment.HEADER: firstHeader = false; break; case GadgetConfigComponentAlignment.FOOTER: firstFooter = false; break; } } else { AddComponent(new GadgetConfigSeparatorComponent(this, "Seperator"), alignment); } seperatorMade = true; } if (!commentsMade) { foreach (string comment in keyData.Comments) { if (!string.IsNullOrEmpty(comment) && comment[0] != '[') { AddComponent(new GadgetConfigLabelComponent(this, "Comment For: " + keyData.KeyName, comment, 0.05f, true), alignment); } } commentsMade = true; } AddComponent(new GadgetConfigSpacerComponent(this, "Parsing Error Spacer for: " + keyData.KeyName)); AddComponent(new GadgetConfigLabelComponent(this, "Parsing Error Label for: " + keyData.KeyName, "An error occured parsing the following config entry: " + keyData.KeyName, 0.05f, true), alignment); AddComponent(new GadgetConfigLabelComponent(this, "Parsing Error Label for: " + keyData.KeyName, "As such, a raw-text editor is being provided as a fallback.", 0.05f, true), alignment); AddComponent(new GadgetConfigLabelComponent(this, "Parsing Error Label for: " + keyData.KeyName, "Additionally, the entry's meta-comments are being displayed.", 0.05f, true), alignment); AddComponent(new GadgetConfigSpacerComponent(this, "Parsing Error Spacer for: " + keyData.KeyName)); foreach (string comment in keyData.Comments) { if (!string.IsNullOrEmpty(comment) && comment[0] == '[') { AddComponent(new GadgetConfigLabelComponent(this, "Meta-Comment For: " + keyData.KeyName, comment, 0.05f, true), alignment); } } AddComponent(new GadgetConfigStringComponent(this, keyData.KeyName, keyData.Value, (s) => SetConfigValue(section, keyData.KeyName, s), readonlyEntries.Contains(keyData.KeyName)), alignment); } catch (Exception ee) { GadgetCore.CoreLogger.Log("WARNING: Failed fallback parsing of config entry '" + keyData.KeyName + "' with value '" + keyData.Value + "': " + ee); try { if (!seperatorMade) { try { GadgetConfigComponentAlignment?nullableAlignment = AlignConfigEntry(keyData.KeyName); if (nullableAlignment == null) { continue; } alignment = (GadgetConfigComponentAlignment)nullableAlignment; if ((alignment == GadgetConfigComponentAlignment.STANDARD && firstStandard == true) || (alignment == GadgetConfigComponentAlignment.HEADER && firstHeader == true) || (alignment == GadgetConfigComponentAlignment.FOOTER && firstFooter == true)) { switch (alignment) { case GadgetConfigComponentAlignment.STANDARD: firstStandard = false; break; case GadgetConfigComponentAlignment.HEADER: firstHeader = false; break; case GadgetConfigComponentAlignment.FOOTER: firstFooter = false; break; } } else { AddComponent(new GadgetConfigSeparatorComponent(this, "Seperator"), alignment); } seperatorMade = true; } catch (Exception se) { GadgetCore.CoreLogger.Log("WARNING: Failed creating seperator for failed config entry '" + keyData.KeyName + "': " + se); } } AddComponent(new GadgetConfigLabelComponent(this, "Fatal Error Name Label for: " + keyData.KeyName, "A FATAL ERROR OCCURED PROCESSING THE FOLLOWING CONFIG ENTRY: " + keyData.KeyName, 0.05f, true), alignment); AddComponent(new GadgetConfigLabelComponent(this, "Fatal Error Value Label for: " + keyData.KeyName, "THE VALUE OF THE CONFIG ENTRY IS: " + keyData.Value, 0.05f, true), alignment); } catch (Exception fe) { GadgetCore.CoreLogger.Log("WARNING: Failed creating label for failed config entry '" + keyData.KeyName + "': " + fe); } } } } }
/// <summary> /// Adds a <see cref="GadgetConfigComponent"/> to this config menu, at a specified index in the component array. May only be called before the menu is built. /// </summary> public virtual void InsertComponent(int index, GadgetConfigComponent component, GadgetConfigComponentAlignment alignment = GadgetConfigComponentAlignment.STANDARD) { if (HasBuilt) { throw new InvalidOperationException("GadgetConfigComponents may not be added to a BasicGadgetConfigMenu after the menu has been built!"); } component.SetAlignment(alignment); ConfigComponents.Insert(Math.Min(index, ConfigComponents.Count - 1), component); }