using PluginLoader.Data; using PluginLoader.GUI.GuiControls; using PluginLoader.Stats; using PluginLoader.Stats.Model; using Sandbox.Graphics.GUI; using VRage.Game; using VRage.Utils; using VRageMath; namespace PluginLoader.GUI; public class PluginDetailsPanel : MyGuiControlParent { private readonly PluginStat dummyStat = new(); private readonly MyGuiScreenPluginConfig pluginsDialog; private MyGuiControlLabel authorLabel; private MyGuiControlLabel authorText; private MyGuiControlButton configButton; private MyGuiControlCompositePanel descriptionPanel; private MyGuiControlMultilineText descriptionText; private MyGuiControlButton downvoteButton; private MyGuiControlLabel downvoteCountText; private MyGuiControlImage downvoteIcon; private MyGuiControlCheckbox enableCheckbox; private MyGuiControlLabel enableLabel; private MyGuiControlButton infoButton; private PluginInstance instance; // Layout management private MyLayoutTable layoutTable; // Plugin currently loaded into the panel or null if none are loaded private PluginData plugin; // Panel controls private MyGuiControlLabel pluginNameLabel; private MyGuiControlLabel pluginNameText; private RatingControl ratingControl; private MyGuiControlLabel ratingLabel; private MyGuiControlLabel statusLabel; private MyGuiControlLabel statusText; private MyGuiControlButton upvoteButton; private MyGuiControlLabel upvoteCountText; private MyGuiControlImage upvoteIcon; private MyGuiControlLabel usageLabel; private MyGuiControlLabel usageText; private MyGuiControlLabel versionLabel; private MyGuiControlLabel versionText; public PluginDetailsPanel(MyGuiScreenPluginConfig dialog) { pluginsDialog = dialog; } public PluginData Plugin { get => plugin; set { if (ReferenceEquals(value, Plugin)) return; plugin = value; if (plugin == null) { DisableControls(); ClearPluginData(); return; } if (Main.Instance.TryGetPluginInstance(plugin.Id, out var instance)) this.instance = instance; else this.instance = null; EnableControls(); LoadPluginData(); } } private PluginStat PluginStat => pluginsDialog.PluginStats?.Stats.GetValueOrDefault(plugin.Id) ?? dummyStat; public event Action OnPluginToggled; private void DisableControls() { foreach (var control in Controls) control.Enabled = false; } private void EnableControls() { foreach (var control in Controls) control.Enabled = true; } private void ClearPluginData() { pluginNameText.Text = ""; authorText.Text = ""; versionText.Text = ""; statusText.Text = ""; usageText.Text = ""; ratingControl.Value = 0; upvoteButton.Checked = false; downvoteButton.Checked = false; descriptionText.Text.Clear(); enableCheckbox.IsChecked = false; } public void LoadPluginData() { if (plugin == null) return; var stat = PluginStat; var vote = stat.Vote; var nonLocal = !plugin.IsLocal; var canVote = (plugin.Enabled || stat.Tried) && nonLocal; var showVotes = canVote || nonLocal; pluginNameText.Text = plugin.FriendlyName ?? "N/A"; authorText.Text = plugin.Author ?? (plugin.IsLocal ? "Local" : "N/A"); versionText.Text = plugin.Version?.ToString() ?? "N/A"; statusLabel.Visible = nonLocal; statusText.Visible = nonLocal; statusText.Text = plugin.Status == PluginStatus.None ? plugin.Enabled ? "Up to date" : "N/A" : plugin.StatusString; usageLabel.Visible = nonLocal; usageText.Visible = nonLocal; usageText.Text = stat.Players.ToString(); ratingLabel.Visible = showVotes; upvoteIcon.Visible = showVotes; upvoteButton.Visible = canVote; upvoteButton.Checked = vote > 0; upvoteCountText.Visible = showVotes; upvoteCountText.Text = $"{stat.Upvotes}"; downvoteIcon.Visible = showVotes; downvoteButton.Visible = canVote; downvoteButton.Checked = vote < 0; downvoteCountText.Visible = showVotes; downvoteCountText.Text = $"{stat.Downvotes}"; ratingControl.Value = stat.Rating; plugin.GetDescriptionText(descriptionText); descriptionPanel.Visible = descriptionText.Visible; enableCheckbox.IsChecked = pluginsDialog.AfterRebootEnableFlags[plugin.Id]; configButton.Enabled = instance != null && instance.HasConfigDialog; configButton.Visible = instance != null; } public virtual void CreateControls(Vector2 rightSideOrigin) { // Plugin name pluginNameLabel = new() { Text = "Name", OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; pluginNameText = new() { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; // Author authorLabel = new() { Text = "Author", OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; authorText = new() { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; // Version versionLabel = new() { Text = "Version", OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; versionText = new() { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; // Status statusLabel = new() { Text = "Status", OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; statusText = new() { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; // Usage usageLabel = new() { Text = "Usage", OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; usageText = new() { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; // Rating ratingLabel = new() { Text = "Rating", OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; upvoteButton = new(null, MyGuiControlButtonStyleEnum.Rectangular, onButtonClick: OnRateUpClicked, size: new Vector2(0.03f)) { CanHaveFocus = false }; upvoteIcon = CreateRateIcon(upvoteButton, "Textures\\GUI\\Icons\\Blueprints\\like_test.png"); upvoteIcon.CanHaveFocus = false; upvoteCountText = new() { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; downvoteButton = new(null, MyGuiControlButtonStyleEnum.Rectangular, onButtonClick: OnRateDownClicked, size: new Vector2(0.03f)) { CanHaveFocus = false }; downvoteIcon = CreateRateIcon(downvoteButton, "Textures\\GUI\\Icons\\Blueprints\\dislike_test.png"); downvoteIcon.CanHaveFocus = false; downvoteCountText = new() { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; ratingControl = new() { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, Visible = false // FIXME: Make the rating (stars) visible later! Its positioning should already be good. }; // Plugin description descriptionText = new() { Name = "DescriptionText", OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, TextAlign = MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, TextBoxAlign = MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP }; descriptionText.OnLinkClicked += (x, url) => MyGuiSandbox.OpenUrl(url, UrlOpenMode.SteamOrExternalWithConfirm); descriptionPanel = new() { BackgroundTexture = MyGuiConstants.TEXTURE_RECTANGLE_DARK_BORDER }; // Enable checkbox enableLabel = new() { Text = "Enabled", OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP }; enableCheckbox = new(toolTip: "Enables loading the plugin when SE is started.") { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, Enabled = false }; enableCheckbox.IsCheckedChanged += TogglePlugin; // Info button infoButton = new(onButtonClick: _ => Plugin?.Show()) { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, Text = "Plugin Info" }; // Plugin config button configButton = new(onButtonClick: _ => instance?.OpenConfig()) { OriginAlign = MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_TOP, Text = "Plugin Config" }; LayoutControls(rightSideOrigin); } private void LayoutControls(Vector2 rightSideOrigin) { layoutTable = new(this, rightSideOrigin, new(1f, 1f)); layoutTable.SetColumnWidths(168f, 468f); layoutTable.SetRowHeights(60f, 60f, 60f, 60f, 60f, 60f, 420f, 60f, 60f); var row = 0; layoutTable.Add(pluginNameLabel, MyAlignH.Left, MyAlignV.Center, row, 0); layoutTable.Add(pluginNameText, MyAlignH.Left, MyAlignV.Center, row, 1); row++; layoutTable.Add(authorLabel, MyAlignH.Left, MyAlignV.Center, row, 0); layoutTable.Add(authorText, MyAlignH.Left, MyAlignV.Center, row, 1); row++; layoutTable.Add(versionLabel, MyAlignH.Left, MyAlignV.Center, row, 0); layoutTable.Add(versionText, MyAlignH.Left, MyAlignV.Center, row, 1); row++; layoutTable.Add(statusLabel, MyAlignH.Left, MyAlignV.Center, row, 0); layoutTable.Add(statusText, MyAlignH.Left, MyAlignV.Center, row, 1); row++; layoutTable.Add(usageLabel, MyAlignH.Left, MyAlignV.Center, row, 0); layoutTable.Add(usageText, MyAlignH.Left, MyAlignV.Center, row, 1); row++; layoutTable.Add(ratingLabel, MyAlignH.Left, MyAlignV.Center, row, 0); layoutTable.Add(upvoteCountText, MyAlignH.Left, MyAlignV.Center, row, 1); layoutTable.Add(upvoteButton, MyAlignH.Left, MyAlignV.Center, row, 1); layoutTable.Add(upvoteIcon, MyAlignH.Left, MyAlignV.Center, row, 1); layoutTable.Add(downvoteCountText, MyAlignH.Left, MyAlignV.Center, row, 1); layoutTable.Add(downvoteButton, MyAlignH.Left, MyAlignV.Center, row, 1); layoutTable.Add(downvoteIcon, MyAlignH.Left, MyAlignV.Center, row, 1); layoutTable.Add(ratingControl, MyAlignH.Left, MyAlignV.Center, row, 1); const float counterWidth = 0.05f; const float spacing = 0.005f; var buttonWidth = upvoteButton.Size.X; var voteWidth = buttonWidth + spacing + counterWidth + 3 * spacing; var buttonToIconOffset = new Vector2(0.004f, -0.001f); upvoteIcon.Position = upvoteButton.Position + buttonToIconOffset; upvoteCountText.Position = upvoteButton.Position + new Vector2(buttonWidth + spacing, 0f); downvoteButton.Position = upvoteButton.Position + new Vector2(voteWidth, 0f); downvoteIcon.Position = downvoteButton.Position + buttonToIconOffset; downvoteCountText.Position = downvoteButton.Position + new Vector2(buttonWidth + spacing, 0f); ratingControl.Position = downvoteButton.Position + new Vector2(voteWidth, 0f); row++; layoutTable.AddWithSize(descriptionPanel, MyAlignH.Center, MyAlignV.Top, row, 0, 1, 2); layoutTable.AddWithSize(descriptionText, MyAlignH.Center, MyAlignV.Center, row, 0, 1, 2); row++; layoutTable.Add(enableLabel, MyAlignH.Left, MyAlignV.Center, row, 0); layoutTable.Add(enableCheckbox, MyAlignH.Left, MyAlignV.Center, row, 1); row++; const float infoConfigSpacing = 0.015f; layoutTable.AddWithSize(infoButton, MyAlignH.Right, MyAlignV.Center, row, 0, 1, 2); layoutTable.AddWithSize(configButton, MyAlignH.Right, MyAlignV.Center, row, 0, 1, 2); configButton.Position += new Vector2(0f, infoConfigSpacing); infoButton.Position = configButton.Position + new Vector2(-configButton.Size.X - infoConfigSpacing, 0); // row++; var border = 0.002f * Vector2.One; descriptionPanel.Position -= border; descriptionPanel.Size += 2 * border; DisableControls(); } private void TogglePlugin(MyGuiControlCheckbox obj) { if (plugin == null) return; OnPluginToggled?.Invoke(plugin, enableCheckbox.IsChecked); } private void OnRateUpClicked(MyGuiControlButton button) { Vote(1); } private void OnRateDownClicked(MyGuiControlButton button) { Vote(-1); } private void Vote(int vote) { if (PlayerConsent.ConsentGiven) StoreVote(vote); else PlayerConsent.ShowDialog(() => StoreVote(vote)); } private void StoreVote(int vote) { if (!PlayerConsent.ConsentGiven || pluginsDialog.PluginStats == null) return; var originalStat = PluginStat; if (originalStat.Vote == vote) vote = 0; var updatedStat = StatsClient.Vote(plugin.Id, vote); if (updatedStat == null) return; pluginsDialog.PluginStats.Stats[plugin.Id] = updatedStat; LoadPluginData(); } // From Sandbox.Game.Screens.MyGuiScreenNewWorkshopGame #region Vote buttons private MyGuiControlImage CreateRateIcon(MyGuiControlButton button, string texture) { var myGuiControlImage = new MyGuiControlImage(null, null, null, null, new[] { texture }); AdjustButtonForIcon(button, myGuiControlImage); myGuiControlImage.Size = button.Size * 0.6f; return myGuiControlImage; } private void AdjustButtonForIcon(MyGuiControlButton button, MyGuiControlImage icon) { button.Size = new(button.Size.X, button.Size.X * 4f / 3f); button.HighlightChanged += delegate(MyGuiControlBase control) { icon.ColorMask = control.HasHighlight ? MyGuiConstants.HIGHLIGHT_TEXT_COLOR : Vector4.One; }; } #endregion }