diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index 339ab94a6b..827e837ddc 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -632,7 +632,7 @@ public Dictionary ConflictList public IEnumerable ConflictDescriptions => conflicts.Where(kvp => kvp.Value == null // Pick the pair with the least distantly selected one first - || totalDependers(kvp.Key) < totalDependers(kvp.Value)) + || totalDependers(kvp.Key) <= totalDependers(kvp.Value)) .Select(kvp => string.Format( Properties.Resources.RelationshipResolverConflictsWith, conflictingModDescription(kvp.Key, null), diff --git a/GUI/Controls/ManageMods.cs b/GUI/Controls/ManageMods.cs index 8c33eb8a12..99bfad3424 100644 --- a/GUI/Controls/ManageMods.cs +++ b/GUI/Controls/ManageMods.cs @@ -178,45 +178,44 @@ private void ConflictsUpdated(Dictionary prevConflicts) Main.Instance.AddStatusMessage(""); } + var inst = Main.Instance.CurrentInstance; + var registry = RegistryManager.Instance(inst, repoData).registry; if (prevConflicts != null) { // Mark old conflicts as non-conflicted // (rows that are _still_ conflicted will be marked as such in the next loop) - var inst = Main.Instance.CurrentInstance; foreach (GUIMod guiMod in prevConflicts.Keys) { - DataGridViewRow row = mainModList.full_list_of_mod_rows[guiMod.Identifier]; - - foreach (DataGridViewCell cell in row.Cells) - { - cell.ToolTipText = null; - } - var registry = RegistryManager.Instance(Main.Instance.CurrentInstance, repoData).registry; - mainModList.ReapplyLabels(guiMod, false, inst.Name, inst.game, registry); - if (row.Visible) - { - ModGrid.InvalidateRow(row.Index); - } + SetUnsetRowConflicted(guiMod, false, null, inst, registry); } } if (Conflicts != null) { // Mark current conflicts as conflicted - foreach (var kvp in Conflicts) + foreach ((GUIMod guiMod, string conflict_text) in Conflicts) { - GUIMod guiMod = kvp.Key; - DataGridViewRow row = mainModList.full_list_of_mod_rows[guiMod.Identifier]; - string conflict_text = kvp.Value; + SetUnsetRowConflicted(guiMod, true, conflict_text, inst, registry); + } + } + } - foreach (DataGridViewCell cell in row.Cells) - { - cell.ToolTipText = conflict_text; - } - row.DefaultCellStyle.BackColor = mainModList.GetRowBackground(guiMod, true, Main.Instance.CurrentInstance.Name); - if (row.Visible) - { - ModGrid.InvalidateRow(row.Index); - } + private void SetUnsetRowConflicted(GUIMod guiMod, + bool conflicted, + string tooltip, + GameInstance inst, + Registry registry) + { + var row = mainModList.ReapplyLabels(guiMod, conflicted, + inst.Name, inst.game, registry); + if (row != null) + { + foreach (DataGridViewCell cell in row.Cells) + { + cell.ToolTipText = tooltip; + } + if (row.Visible) + { + ModGrid.InvalidateRow(row.Index); } } } @@ -291,6 +290,7 @@ private void LabelsContextMenuStrip_Opening(object sender, CancelEventArgs e) LabelsContextMenuStrip.Items.Add( new ToolStripMenuItem(mlbl.Name, null, labelMenuItem_Click) { + BackColor = mlbl.Color, Checked = mlbl.ContainsModule(Main.Instance.CurrentInstance.game, module.Identifier), CheckOnClick = true, Tag = mlbl, diff --git a/GUI/Model/ModList.cs b/GUI/Model/ModList.cs index 21d1c7ede2..8d605dc3bd 100644 --- a/GUI/Model/ModList.cs +++ b/GUI/Model/ModList.cs @@ -288,13 +288,8 @@ private DataGridViewRow MakeRow(GUIMod mod, List changes, string inst { DataGridViewRow item = new DataGridViewRow() {Tag = mod}; - Color? myColor = ModuleLabels.LabelsFor(instanceName) - .FirstOrDefault(l => l.ContainsModule(game, mod.Identifier)) - ?.Color; - if (myColor.HasValue) - { - item.DefaultCellStyle.BackColor = myColor.Value; - } + item.DefaultCellStyle.BackColor = GetRowBackground(mod, false, instanceName, game); + item.DefaultCellStyle.SelectionBackColor = SelectionBlend(item.DefaultCellStyle.BackColor); ModChange myChange = changes?.FindLast((ModChange ch) => ch.Mod.Equals(mod)); @@ -380,29 +375,41 @@ private DataGridViewRow MakeRow(GUIMod mod, List changes, string inst private static string ToGridText(string text) => Platform.IsMono ? text.Replace("&", "&&") : text; - public Color GetRowBackground(GUIMod mod, bool conflicted, string instanceName) - => conflicted ? Color.LightCoral - : full_list_of_mod_rows.ContainsKey(mod.Identifier) - ? ModuleLabels.LabelsFor(instanceName) - .FirstOrDefault(l => l.ContainsModule(Main.Instance.CurrentInstance.game, mod.Identifier)) - ?.Color - ?? Color.Empty - : Color.Empty; + public Color GetRowBackground(GUIMod mod, bool conflicted, string instanceName, IGame game) + => conflicted ? conflictColor + : Util.BlendColors( + ModuleLabels.LabelsFor(instanceName) + .Where(l => l.ContainsModule(game, mod.Identifier)) + .Select(l => l.Color) + .ToArray()); + + private static readonly Color conflictColor = Color.FromArgb(255, 64, 64); /// /// Update the color and visible state of the given row /// after it has been added to or removed from a label group /// /// The mod that needs an update - public void ReapplyLabels(GUIMod mod, bool conflicted, string instanceName, IGame game, Registry registry) + public DataGridViewRow ReapplyLabels(GUIMod mod, bool conflicted, + string instanceName, IGame game, Registry registry) { if (full_list_of_mod_rows.TryGetValue(mod.Identifier, out DataGridViewRow row)) { - row.DefaultCellStyle.BackColor = GetRowBackground(mod, conflicted, instanceName); + row.DefaultCellStyle.BackColor = GetRowBackground(mod, conflicted, instanceName, game); + row.DefaultCellStyle.SelectionBackColor = SelectionBlend(row.DefaultCellStyle.BackColor); row.Visible = IsVisible(mod, instanceName, game, registry); + return row; } + return null; } + private static Color SelectionBlend(Color c) + => c == Color.Empty + ? SystemColors.Highlight + : SystemColors.Highlight.AlphaBlendWith(selectionAlpha, c); + + private const float selectionAlpha = 0.4f; + /// /// Returns a version string shorn of any leading epoch as delimited by a single colon /// diff --git a/GUI/Util.cs b/GUI/Util.cs index f90c593d64..ed22293fb5 100644 --- a/GUI/Util.cs +++ b/GUI/Util.cs @@ -290,6 +290,25 @@ public static EventHandler Debounce( }; } + public static Color BlendColors(Color[] colors) + => colors.Length < 1 ? Color.Empty + : colors.Length == 1 ? colors[0] + : colors.Aggregate((back, fore) => fore.AlphaBlendWith(1f / colors.Length, back)); + + public static Color AlphaBlendWith(this Color c1, float alpha, Color c2) + => AddColors(c1.MultiplyBy(alpha), + c2.MultiplyBy(1f - alpha)); + + private static Color MultiplyBy(this Color c, float f) + => Color.FromArgb((int)(f * c.R), + (int)(f * c.G), + (int)(f * c.B)); + + private static Color AddColors(Color a, Color b) + => Color.FromArgb(a.R + b.R, + a.G + b.G, + a.B + b.B); + /// /// Simple syntactic sugar around Graphics.MeasureString ///