/// <summary> /// Fills in the actual mod option fields. /// </summary> /// <param name="dialog">The dialog to populate.</param> private void FillModOptions(PDialog dialog) { var body = dialog.Body; var margin = new RectOffset(CATEGORY_MARGIN, CATEGORY_MARGIN, CATEGORY_MARGIN, CATEGORY_MARGIN); // For each option, add its UI component to panel body.Margin = new RectOffset(); var scrollBody = new PPanel("ScrollContent") { Spacing = OUTER_MARGIN, Direction = PanelDirection.Vertical, Alignment = TextAnchor.UpperCenter, FlexSize = Vector2.right }; var allOptions = (options == null) ? optionCategories : OptionsEntry. AddCustomOptions(options, optionCategories); // Display all categories foreach (var catEntries in allOptions) { string category = catEntries.Key; if (catEntries.Value.Count > 0) { string name = string.IsNullOrEmpty(category) ? "Default" : category; int i = 0; // Not optimal for layout performance, but the panel is needed to have a // different background color for each category "box" var container = new PGridPanel("Category_" + name) { Margin = margin, BackColor = PUITuning.Colors.DialogDarkBackground, FlexSize = Vector2.right }; // Needs to be a separate panel so that it can be collapsed var contents = new PGridPanel("Entries") { FlexSize = Vector2.right }; AddCategoryHeader(container, catEntries.Key, contents); foreach (var entry in catEntries.Value) { contents.AddRow(new GridRowSpec()); entry.CreateUIEntry(contents, ref i); i++; } scrollBody.AddChild(container); } } // Manual config button scrollBody.AddChild(new PButton("ManualConfig") { Text = PUIStrings.BUTTON_MANUAL, ToolTip = PUIStrings.TOOLTIP_MANUAL, OnClick = OnManualConfig, TextAlignment = TextAnchor.MiddleCenter, Margin = PDialog.BUTTON_MARGIN }.SetKleiBlueStyle()); body.AddChild(new PScrollPane() { ScrollHorizontal = false, ScrollVertical = allOptions.Count > 0, Child = scrollBody, FlexSize = Vector2.right, TrackSize = 8, AlwaysShowHorizontal = false, AlwaysShowVertical = false }); }
/// <summary> /// Creates an options entry wrapper for the specified property. /// </summary> /// <param name="info">The property to wrap.</param> /// <param name="oa">The option title and tool tip.</param> /// <returns>An options wrapper, or null if none can handle this type.</returns> private static OptionsEntry CreateOptions(PropertyInfo info, OptionAttribute oa) { OptionsEntry entry = null; Type type = info.PropertyType; string field = info.Name; // Enumeration type if (type.IsEnum) { entry = new SelectOneOptionsEntry(field, oa.Title, oa.Tooltip, type); } else if (type == typeof(bool)) { entry = new CheckboxOptionsEntry(field, oa.Title, oa.Tooltip); } else if (type == typeof(int)) { entry = new IntOptionsEntry(oa.Title, oa.Tooltip, info); } else if (type == typeof(float)) { entry = new FloatOptionsEntry(oa.Title, oa.Tooltip, info); } else if (type == typeof(string)) { entry = new StringOptionsEntry(oa.Title, oa.Tooltip, info); } return(entry); }
/// <summary> /// Builds the options entries from the type. /// </summary> /// <param name="forType">The type of the options class.</param> /// <returns>A list of all public properties annotated for options dialogs.</returns> private static IDictionary <string, OptionsList> BuildOptions(Type forType) { var entries = new Dictionary <string, OptionsList>(4); OptionAttribute oa; foreach (var prop in forType.GetProperties()) { // Must have the annotation foreach (var attr in prop.GetCustomAttributes(false)) { if ((oa = OptionsEntry.GetOptionInfo(attr)) != null) { // Attempt to find a class that will represent it var entry = CreateOptions(prop, oa); if (entry != null) { string category = entry.Category ?? ""; // Add this category if it does not exist if (!entries.TryGetValue(category, out OptionsList inCat)) { inCat = new List <OptionsEntry>(16); entries.Add(category, inCat); } inCat.Add(entry); } break; } } } return(entries); }
internal OptionsDialog(Type optionsType, KMod.Mod modSpec) { dialog = null; modImage = null; this.modSpec = modSpec ?? throw new ArgumentNullException("modSpec"); this.optionsType = optionsType ?? throw new ArgumentNullException("optionsType"); optionCategories = OptionsEntry.BuildOptions(optionsType); options = null; // Determine config location infoAttr = POptions.GetModInfoAttribute(optionsType); typeAttr = POptions.GetConfigFileAttribute(optionsType); var src = modSpec.file_source; if (src == null) { path = null; } else { path = Path.Combine(src.GetRoot(), typeAttr?.ConfigFileName ?? POptions. CONFIG_FILE_NAME); } // Find mod home page string url = infoAttr?.URL; var label = modSpec.label; if (string.IsNullOrEmpty(url) && label.distribution_platform == KMod.Label. DistributionPlatform.Steam) { // Steam mods use their workshop ID as the label url = "https://steamcommunity.com/sharedfiles/filedetails/?id=" + label.id; } modURL = url; }
internal OptionsDialog(Type optionsType, IOptionsHandler handler) { string root = handler.ConfigPath; dialog = null; modImage = null; this.handler = handler ?? throw new ArgumentNullException("handler"); this.optionsType = optionsType ?? throw new ArgumentNullException("optionsType"); optionCategories = OptionsEntry.BuildOptions(optionsType); options = null; // Determine config location infoAttr = POptions.GetModInfoAttribute(optionsType); typeAttr = POptions.GetConfigFileAttribute(optionsType); path = (root == null) ? null : Path.Combine(root, typeAttr?.ConfigFileName ?? POptions.CONFIG_FILE_NAME); }
/// <summary> /// Adds a category header to the dialog. /// </summary> /// <param name="container">The parent of the header.</param> /// <param name="category">The header title.</param> /// <param name="contents">The panel containing the options in this category.</param> private void AddCategoryHeader(PGridPanel container, string category, PGridPanel contents) { contents.AddColumn(new GridColumnSpec(flex: 1.0f)).AddColumn(new GridColumnSpec()); if (!string.IsNullOrEmpty(category)) { bool state = !(infoAttr?.ForceCollapseCategories ?? false); var handler = new CategoryExpandHandler(state); container.AddColumn(new GridColumnSpec()).AddColumn(new GridColumnSpec( flex: 1.0f)).AddRow(new GridRowSpec()).AddRow(new GridRowSpec(flex: 1.0f)); // Toggle is upper left, header is upper right var header = new PLabel("CategoryHeader") { Text = OptionsEntry.LookInStrings(category), TextStyle = CATEGORY_TITLE_STYLE, TextAlignment = TextAnchor.LowerCenter }; var toggle = new PToggle("CategoryToggle") { Color = PUITuning.Colors.ComponentDarkStyle, InitialState = state, ToolTip = PUIStrings.TOOLTIP_TOGGLE, Size = TOGGLE_SIZE, OnStateChanged = handler.OnExpandContract }; header.OnRealize += handler.OnRealizeHeader; toggle.OnRealize += handler.OnRealizeToggle; container.AddChild(header, new GridComponentSpec(0, 1) { Margin = new RectOffset(OUTER_MARGIN, OUTER_MARGIN, 0, 0) }).AddChild(toggle, new GridComponentSpec(0, 0)); if (contents != null) { contents.OnRealize += handler.OnRealizePanel; } container.AddChild(contents, new GridComponentSpec(1, 0) { ColumnSpan = 2 }); } else { // Default of unconstrained fills the whole panel container.AddColumn(new GridColumnSpec(flex: 1.0f)).AddRow(new GridRowSpec( flex: 1.0f)).AddChild(contents, new GridComponentSpec(0, 0)); } }
/// <summary> /// Triggered when the Mod Options button is clicked. /// </summary> public void ShowDialog() { string title; if (string.IsNullOrEmpty(displayInfo.Title)) { title = PLibStrings.BUTTON_OPTIONS; } else { title = string.Format(PLibStrings.DIALOG_TITLE, OptionsEntry.LookInStrings( displayInfo.Title)); } // Close current dialog if open CloseDialog(); // Ensure that it is on top of other screens (which may be +100 modal) var pDialog = new PDialog("ModOptions") { Title = title, Size = SETTINGS_DIALOG_SIZE, SortKey = 150.0f, DialogBackColor = PUITuning.Colors.OptionsBackground, DialogClosed = OnOptionsSelected, MaxSize = SETTINGS_DIALOG_MAX_SIZE, RoundToNearestEven = true }.AddButton("ok", STRINGS.UI.CONFIRMDIALOG.OK, PLibStrings.TOOLTIP_OK, PUITuning.Colors.ButtonPinkStyle).AddButton(PDialog.DIALOG_KEY_CLOSE, STRINGS.UI.CONFIRMDIALOG.CANCEL, PLibStrings.TOOLTIP_CANCEL, PUITuning.Colors.ButtonBlueStyle); options = POptions.ReadSettings(POptions.GetConfigFilePath(optionsType), optionsType); if (options == null) { options = CreateOptions(optionsType); } AddModInfoScreen(pDialog); FillModOptions(pDialog); // Manually build the dialog so the options can be updated after realization var obj = pDialog.Build(); UpdateOptions(); dialog = obj.GetComponent <KScreen>(); dialog.Activate(); }
/// <summary> /// Triggered when the Mod Options button is clicked. /// </summary> public void OnModOptions(GameObject _) { if (path != null) { string title = handler.GetTitle(OptionsEntry.LookInStrings(infoAttr?.Title)); if (string.IsNullOrEmpty(title)) { title = PUIStrings.BUTTON_OPTIONS; } // Close current dialog if open CloseDialog(); // Ensure that it is on top of other screens (which may be +100 modal) var pDialog = new PDialog("ModOptions") { Title = title, Size = SETTINGS_DIALOG_SIZE, SortKey = 150.0f, DialogBackColor = PUITuning.Colors.OptionsBackground, DialogClosed = OnOptionsSelected, MaxSize = SETTINGS_DIALOG_MAX_SIZE, RoundToNearestEven = true }.AddButton("ok", STRINGS.UI.CONFIRMDIALOG.OK, PUIStrings.TOOLTIP_OK, PUITuning.Colors.ButtonPinkStyle).AddButton(PDialog.DIALOG_KEY_CLOSE, STRINGS.UI.CONFIRMDIALOG.CANCEL, PUIStrings.TOOLTIP_CANCEL, PUITuning.Colors.ButtonBlueStyle); options = POptions.ReadSettings(path, optionsType); if (options == null) { CreateOptions(); } if (infoAttr != null) { AddModInfoScreen(pDialog); } FillModOptions(pDialog); // Manually build the dialog so the options can be updated after realization var obj = pDialog.Build(); UpdateOptions(); dialog = obj.GetComponent <KScreen>(); dialog.Activate(); } }
internal OptionsDialog(Type optionsType) { OnClose = null; dialog = null; modImage = null; this.optionsType = optionsType ?? throw new ArgumentNullException(nameof( optionsType)); optionCategories = OptionsEntry.BuildOptions(optionsType); options = null; // Determine config location var infoAttr = optionsType.GetCustomAttribute <ModInfoAttribute>(); if (infoAttr != null) { collapseCategories = infoAttr.ForceCollapseCategories; } else { collapseCategories = false; } configAttr = optionsType.GetCustomAttribute <ConfigFileAttribute>(); displayInfo = new ModDialogInfo(optionsType, infoAttr?.URL, infoAttr?.Image); }
/// <summary> /// Builds the options entries from the type. /// </summary> /// <param name="forType">The type of the options class.</param> /// <returns>A list of all public properties annotated for options dialogs.</returns> private static ICollection <OptionsEntry> BuildOptions(Type forType) { var entries = new List <OptionsEntry>(16); OptionAttribute oa; foreach (var prop in forType.GetProperties()) { // Must have the annotation foreach (var attr in prop.GetCustomAttributes(false)) { if ((oa = OptionsEntry.GetTitle(attr)) != null) { // Attempt to find a class that will represent it var entry = CreateOptions(prop, oa); if (entry != null) { entries.Add(entry); } break; } } } return(entries); }
internal RuntimeOptionsHandler(string path, string title) { ConfigPath = path; this.title = string.IsNullOrEmpty(title) ? null : OptionsEntry.LookInStrings(title); }