From 4f4c67735d45bf20f62b9b334e19a6294a9e4b7b Mon Sep 17 00:00:00 2001 From: ScottNZ Date: Sun, 23 Nov 2014 19:58:57 +1300 Subject: [PATCH] Overhaul the lobby faction dropdown for ra --- OpenRA.Game/Traits/World/Country.cs | 6 +++ OpenRA.Game/Widgets/ButtonWidget.cs | 19 ++++--- OpenRA.Game/Widgets/DropDownButtonWidget.cs | 33 ++++++++---- OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 1 + .../Widgets/Logic/ButtonTooltipLogic.cs | 5 +- .../Widgets/Logic/CountryTooltipLogic.cs | 52 +++++++++++++++++++ OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs | 21 +++++--- OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs | 32 +++++++----- mods/ra/chrome/lobby-playerbin.yaml | 21 +++++++- mods/ra/chrome/lobby.yaml | 2 +- mods/ra/chrome/tooltips.yaml | 19 +++++++ mods/ra/rules/world.yaml | 14 +++++ 12 files changed, 185 insertions(+), 40 deletions(-) create mode 100644 OpenRA.Mods.Common/Widgets/Logic/CountryTooltipLogic.cs diff --git a/OpenRA.Game/Traits/World/Country.cs b/OpenRA.Game/Traits/World/Country.cs index 2921e04fc1..9d3ba3c395 100644 --- a/OpenRA.Game/Traits/World/Country.cs +++ b/OpenRA.Game/Traits/World/Country.cs @@ -18,6 +18,12 @@ namespace OpenRA.Traits [Desc("This is the internal name for owner checks.")] public readonly string Race = null; + [Desc("The side that the country belongs to. For example, England belongs to the 'Allies' side.")] + public readonly string Side = null; + + [Translate] + public readonly string Description = null; + public readonly bool Selectable = true; } diff --git a/OpenRA.Game/Widgets/ButtonWidget.cs b/OpenRA.Game/Widgets/ButtonWidget.cs index c041f790fa..b6e564ed58 100644 --- a/OpenRA.Game/Widgets/ButtonWidget.cs +++ b/OpenRA.Game/Widgets/ButtonWidget.cs @@ -47,6 +47,7 @@ namespace OpenRA.Widgets public readonly string TooltipContainer; public readonly string TooltipTemplate = "BUTTON_TOOLTIP"; [Translate] public string TooltipText; + public Func GetTooltipText; // Equivalent to OnMouseUp, but without an input arg public Action OnClick = () => {}; @@ -68,6 +69,7 @@ namespace OpenRA.Widgets OnKeyPress = _ => OnClick(); IsDisabled = () => Disabled; IsHighlighted = () => Highlighted; + GetTooltipText = () => TooltipText; tooltipContainer = Exts.Lazy(() => Ui.Root.Get(TooltipContainer)); } @@ -102,6 +104,7 @@ namespace OpenRA.Widgets TooltipTemplate = other.TooltipTemplate; TooltipText = other.TooltipText; + GetTooltipText = other.GetTooltipText; TooltipContainer = other.TooltipContainer; tooltipContainer = Exts.Lazy(() => Ui.Root.Get(TooltipContainer)); @@ -177,19 +180,23 @@ namespace OpenRA.Widgets public override void MouseEntered() { - if (TooltipContainer == null) return; + if (TooltipContainer == null || GetTooltipText() == null) + return; + tooltipContainer.Value.SetTooltip(TooltipTemplate, - new WidgetArgs() {{ "button", this }}); + new WidgetArgs { { "button", this } }); } public override void MouseExited() { - if (TooltipContainer == null) return; + if (TooltipContainer == null || !tooltipContainer.IsValueCreated) + return; + tooltipContainer.Value.RemoveTooltip(); } public override int2 ChildOrigin { get { return RenderOrigin + - ((Depressed) ? new int2(VisualHeight, VisualHeight) : new int2(0, 0)); } } + (Depressed ? new int2(VisualHeight, VisualHeight) : new int2(0, 0)); } } public override void Draw() { @@ -209,10 +216,10 @@ namespace OpenRA.Widgets DrawBackground(rb, disabled, Depressed, Ui.MouseOverWidget == this, highlighted); if (Contrast) font.DrawTextWithContrast(text, position + stateOffset, - disabled ? colordisabled : color, contrast, 2); + disabled ? colordisabled : color, contrast, 2); else font.DrawText(text, position + stateOffset, - disabled ? colordisabled : color); + disabled ? colordisabled : color); } public override Widget Clone() { return new ButtonWidget(this); } diff --git a/OpenRA.Game/Widgets/DropDownButtonWidget.cs b/OpenRA.Game/Widgets/DropDownButtonWidget.cs index 06c11d07d5..103ad10d8a 100644 --- a/OpenRA.Game/Widgets/DropDownButtonWidget.cs +++ b/OpenRA.Game/Widgets/DropDownButtonWidget.cs @@ -19,12 +19,19 @@ namespace OpenRA.Widgets { Widget panel; MaskWidget fullscreenMask; + Widget panelRoot; + + public string PanelRoot; [ObjectCreator.UseCtor] public DropDownButtonWidget(Ruleset modRules) : base(modRules) { } - protected DropDownButtonWidget(DropDownButtonWidget widget) : base(widget) { } + protected DropDownButtonWidget(DropDownButtonWidget widget) + : base(widget) + { + PanelRoot = widget.PanelRoot; + } public override void Draw() { @@ -34,7 +41,7 @@ namespace OpenRA.Widgets var image = ChromeProvider.GetImage("scrollbar", IsDisabled() ? "down_pressed" : "down_arrow"); var rb = RenderBounds; var color = GetColor(); - var colordisabled = GetColorDisabled(); + var colorDisabled = GetColorDisabled(); WidgetUtils.DrawRGBA( image, stateOffset + new float2( rb.Right - rb.Height + 4, @@ -42,7 +49,7 @@ namespace OpenRA.Widgets WidgetUtils.FillRectWithColor(new Rectangle(stateOffset.X + rb.Right - rb.Height, stateOffset.Y + rb.Top + 3, 1, rb.Height - 6), - IsDisabled() ? colordisabled : color); + IsDisabled() ? colorDisabled : color); } public override Widget Clone() { return new DropDownButtonWidget(this); } @@ -61,8 +68,8 @@ namespace OpenRA.Widgets if (panel == null) return; - Ui.Root.RemoveChild(fullscreenMask); - Ui.Root.RemoveChild(panel); + panelRoot.RemoveChild(fullscreenMask); + panelRoot.RemoveChild(panel); panel = fullscreenMask = null; } @@ -80,11 +87,17 @@ namespace OpenRA.Widgets if (onCancel != null) fullscreenMask.OnMouseDown += _ => onCancel(); - Ui.Root.AddChild(fullscreenMask); + panelRoot = PanelRoot == null ? Ui.Root : Ui.Root.Get(PanelRoot); + + panelRoot.AddChild(fullscreenMask); var oldBounds = panel.Bounds; - panel.Bounds = new Rectangle(RenderOrigin.X, RenderOrigin.Y + Bounds.Height, oldBounds.Width, oldBounds.Height); - Ui.Root.AddChild(panel); + panel.Bounds = new Rectangle( + RenderOrigin.X - panelRoot.RenderOrigin.X, + RenderOrigin.Y + Bounds.Height - panelRoot.RenderOrigin.Y, + oldBounds.Width, + oldBounds.Height); + panelRoot.AddChild(panel); } public void ShowDropDown(string panelTemplate, int maxHeight, IEnumerable options, Func setupItem) @@ -116,14 +129,14 @@ namespace OpenRA.Widgets var panel = (ScrollPanelWidget)Ui.LoadWidget(panelTemplate, null, new WidgetArgs() {{ "substitutions", substitutions }}); - var headerTemplate = panel.Get("HEADER"); + var headerTemplate = panel.GetOrNull("HEADER"); var itemTemplate = panel.Get("TEMPLATE"); panel.RemoveChildren(); foreach (var kv in groups) { var group = kv.Key; - if (group.Length > 0) + if (group.Length > 0 && headerTemplate != null) { var header = ScrollItemWidget.Setup(headerTemplate, () => true, () => {}); header.Get("LABEL").GetText = () => group; diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 1d84308a23..8422180fe9 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -221,6 +221,7 @@ + diff --git a/OpenRA.Mods.Common/Widgets/Logic/ButtonTooltipLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ButtonTooltipLogic.cs index df1be7331c..3c94c6065b 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ButtonTooltipLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ButtonTooltipLogic.cs @@ -19,9 +19,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic { var label = widget.Get("LABEL"); var font = Game.Renderer.Fonts[label.Font]; - var labelWidth = font.Measure(button.TooltipText).X; + var text = button.GetTooltipText(); + var labelWidth = font.Measure(text).X; - label.GetText = () => button.TooltipText; + label.GetText = () => text; label.Bounds.Width = labelWidth; widget.Bounds.Width = 2 * label.Bounds.X + labelWidth; diff --git a/OpenRA.Mods.Common/Widgets/Logic/CountryTooltipLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/CountryTooltipLogic.cs new file mode 100644 index 0000000000..d5f51e46e5 --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/CountryTooltipLogic.cs @@ -0,0 +1,52 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Linq; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Common.Widgets.Logic +{ + public class CountryTooltipLogic + { + [ObjectCreator.UseCtor] + public CountryTooltipLogic(Widget widget, ButtonWidget button) + { + var lines = button.GetTooltipText().Replace("\\n", "\n").Split('\n'); + + var header = widget.Get("HEADER"); + var headerLine = lines[0]; + var headerFont = Game.Renderer.Fonts[header.Font]; + var headerSize = headerFont.Measure(headerLine); + header.Bounds.Width += headerSize.X; + header.Bounds.Height += headerSize.Y; + header.GetText = () => headerLine; + + if (lines.Length > 1) + { + var description = widget.Get("DESCRIPTION"); + var descriptionLines = lines.Skip(1).ToArray(); + var descriptionFont = Game.Renderer.Fonts[description.Font]; + description.Bounds.Y += header.Bounds.Y + header.Bounds.Height; + description.Bounds.Width += descriptionLines.Select(l => descriptionFont.Measure(l).X).Max(); + description.Bounds.Height += descriptionFont.Measure(descriptionLines.First()).Y * descriptionLines.Length; + description.GetText = () => string.Join("\n", descriptionLines); + + widget.Bounds.Width = Math.Max(header.Bounds.X + header.Bounds.Width, description.Bounds.X + description.Bounds.Width); + widget.Bounds.Height = description.Bounds.Y + description.Bounds.Height; + } + else + { + widget.Bounds.Width = header.Bounds.X + header.Bounds.Width; + widget.Bounds.Height = header.Bounds.Y + header.Bounds.Height; + } + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index ffc9332412..2802bd7f23 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -49,7 +49,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic readonly Widget chatTemplate; readonly ScrollPanelWidget players; - readonly Dictionary countryNames; + + readonly Dictionary countries = new Dictionary(); readonly ColorPreviewManagerWidget colorPreview; @@ -142,10 +143,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic colorPreview = lobby.Get("COLOR_MANAGER"); colorPreview.Color = Game.Settings.Player.Color; - countryNames = modRules.Actors["world"].Traits.WithInterface() - .Where(c => c.Selectable) - .ToDictionary(a => a.Race, a => a.Name); - countryNames.Add("random", "Any"); + countries.Add("random", new LobbyCountry { Name = "Any" }); + foreach (var c in modRules.Actors["world"].Traits.WithInterface().Where(c => c.Selectable)) + countries.Add(c.Race, new LobbyCountry { Name = c.Name, Side = c.Side, Description = c.Description }); var gameStarting = false; Func configurationDisabled = () => !Game.IsHost || gameStarting || @@ -691,7 +691,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic LobbyUtils.SetupEditableNameWidget(template, slot, client, orderManager); LobbyUtils.SetupEditableColorWidget(template, slot, client, orderManager, colorPreview); - LobbyUtils.SetupEditableFactionWidget(template, slot, client, orderManager, countryNames); + LobbyUtils.SetupEditableFactionWidget(template, slot, client, orderManager, countries); LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, Map); LobbyUtils.SetupEditableSpawnWidget(template, slot, client, orderManager, Map); LobbyUtils.SetupEditableReadyWidget(template, slot, client, orderManager, Map); @@ -707,7 +707,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic LobbyUtils.SetupKickWidget(template, slot, client, orderManager, lobby, () => panel = PanelType.Kick, () => panel = PanelType.Players); LobbyUtils.SetupColorWidget(template, slot, client); - LobbyUtils.SetupFactionWidget(template, slot, client, countryNames); + LobbyUtils.SetupFactionWidget(template, slot, client, countries); LobbyUtils.SetupTeamWidget(template, slot, client); LobbyUtils.SetupSpawnWidget(template, slot, client); LobbyUtils.SetupReadyWidget(template, slot, client); @@ -810,4 +810,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic public Action OnClick; } } + + public class LobbyCountry + { + public string Name; + public string Description; + public string Side; + } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs index c022822c1b..b7216a5200 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs @@ -105,21 +105,25 @@ namespace OpenRA.Mods.RA.Widgets.Logic } public static void ShowRaceDropDown(DropDownButtonWidget dropdown, Session.Client client, - OrderManager orderManager, Dictionary countryNames) + OrderManager orderManager, Dictionary countries) { Func setupItem = (race, itemTemplate) => { var item = ScrollItemWidget.Setup(itemTemplate, () => client.Country == race, () => orderManager.IssueOrder(Order.Command("race {0} {1}".F(client.Index, race)))); - item.Get("LABEL").GetText = () => countryNames[race]; + var country = countries[race]; + item.Get("LABEL").GetText = () => country.Name; var flag = item.Get("FLAG"); flag.GetImageCollection = () => "flags"; flag.GetImageName = () => race; + item.GetTooltipText = () => country.Description; return item; }; - dropdown.ShowDropDown("RACE_DROPDOWN_TEMPLATE", 150, countryNames.Keys, setupItem); + var options = countries.GroupBy(c => c.Value.Side).ToDictionary(g => g.Key ?? "", g => g.Select(c => c.Key)); + + dropdown.ShowDropDown("RACE_DROPDOWN_TEMPLATE", 150, options, setupItem); } public static void ShowColorDropDown(DropDownButtonWidget color, Session.Client client, @@ -389,21 +393,25 @@ namespace OpenRA.Mods.RA.Widgets.Logic color.GetColor = () => c.Color.RGB; } - public static void SetupEditableFactionWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, Dictionary countryNames) + public static void SetupEditableFactionWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, + Dictionary countries) { var dropdown = parent.Get("FACTION"); dropdown.IsDisabled = () => s.LockRace || orderManager.LocalClient.IsReady; - dropdown.OnMouseDown = _ => ShowRaceDropDown(dropdown, c, orderManager, countryNames); - SetupFactionWidget(dropdown, s, c, countryNames); + dropdown.OnMouseDown = _ => ShowRaceDropDown(dropdown, c, orderManager, countries); + var factionDescription = countries[c.Country].Description; + dropdown.GetTooltipText = () => factionDescription; + SetupFactionWidget(dropdown, s, c, countries); } - public static void SetupFactionWidget(Widget parent, Session.Slot s, Session.Client c, Dictionary countryNames) + public static void SetupFactionWidget(Widget parent, Session.Slot s, Session.Client c, + Dictionary countries) { - var factionname = parent.Get("FACTIONNAME"); - factionname.GetText = () => countryNames[c.Country]; - var factionflag = parent.Get("FACTIONFLAG"); - factionflag.GetImageName = () => c.Country; - factionflag.GetImageCollection = () => "flags"; + var factionName = parent.Get("FACTIONNAME"); + factionName.GetText = () => countries[c.Country].Name; + var factionFlag = parent.Get("FACTIONFLAG"); + factionFlag.GetImageName = () => c.Country; + factionFlag.GetImageCollection = () => "flags"; } public static void SetupEditableTeamWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, MapPreview map) diff --git a/mods/ra/chrome/lobby-playerbin.yaml b/mods/ra/chrome/lobby-playerbin.yaml index 845f5c6158..364cc53bb5 100644 --- a/mods/ra/chrome/lobby-playerbin.yaml +++ b/mods/ra/chrome/lobby-playerbin.yaml @@ -61,6 +61,9 @@ ScrollPanel@LOBBY_PLAYER_BIN: X: 280 Width: 130 Height: 25 + TooltipContainer: TOOLTIP_CONTAINER + TooltipTemplate: FACTION_DESCRIPTION_TOOLTIP + PanelRoot: FACTION_DROPDOWN_PANEL_ROOT # ensure that tooltips for the options are on top of the dropdown panel Children: Image@FACTIONFLAG: Width: 30 @@ -326,12 +329,27 @@ ScrollPanel@LOBBY_PLAYER_BIN: ScrollPanel@RACE_DROPDOWN_TEMPLATE: Width: DROPDOWN_WIDTH Children: + ScrollItem@HEADER: + BaseName: scrollheader + Width: PARENT_RIGHT-27 + Height: 13 + X: 2 + Y: 0 + Visible: false + Children: + Label@LABEL: + Font: TinyBold + Width: PARENT_RIGHT + Height: 10 + Align: Center ScrollItem@TEMPLATE: Width: PARENT_RIGHT-27 Height: 25 X: 2 Y: 0 Visible: false + TooltipContainer: TOOLTIP_CONTAINER + TooltipTemplate: FACTION_DESCRIPTION_TOOLTIP Children: Image@FLAG: X: 5 @@ -341,5 +359,4 @@ ScrollPanel@RACE_DROPDOWN_TEMPLATE: Label@LABEL: X: 40 Width: 60 - Height: 25 - + Height: 25 \ No newline at end of file diff --git a/mods/ra/chrome/lobby.yaml b/mods/ra/chrome/lobby.yaml index 9320dfdda0..0c16938d15 100644 --- a/mods/ra/chrome/lobby.yaml +++ b/mods/ra/chrome/lobby.yaml @@ -141,5 +141,5 @@ Background@SERVER_LOBBY: Text: Disconnect Font: Bold Key: escape + Container@FACTION_DROPDOWN_PANEL_ROOT: TooltipContainer@TOOLTIP_CONTAINER: - diff --git a/mods/ra/chrome/tooltips.yaml b/mods/ra/chrome/tooltips.yaml index d203552b5d..5b78684304 100644 --- a/mods/ra/chrome/tooltips.yaml +++ b/mods/ra/chrome/tooltips.yaml @@ -185,4 +185,23 @@ Background@SUPPORT_POWER_TOOLTIP: X: 7 Y: 22 Font: TinyBold + VAlign: Top + +Background@FACTION_DESCRIPTION_TOOLTIP: + Logic: CountryTooltipLogic + Background: dialog4 + Children: + Label@HEADER: + X: 7 + Y: 6 + Width: 8 + Height: 12 + Font: Bold + VAlign: Top + Label@DESCRIPTION: + X: 14 + Y: 0 + Width: 15 + Height: 14 + Font: TinyBold VAlign: Top \ No newline at end of file diff --git a/mods/ra/rules/world.yaml b/mods/ra/rules/world.yaml index 448f90e299..3d9681f316 100644 --- a/mods/ra/rules/world.yaml +++ b/mods/ra/rules/world.yaml @@ -95,24 +95,38 @@ World: Country@0: Name: Allies Race: allies + Side: Allies + Description: Allies: Deception\nSpecial Ability: Can build fake structures Country@1: Name: England Race: england + Side: Allies + Description: England: Espionage\nSpecial Unit: British Spy\nSpecial Unit: Phase Transport Country@2: Name: France Race: france + Side: Allies + Description: France: Concealment\nSpecial Structure: Advanced Gap Generator\nSpecial Unit: Mobile Gap Generator Country@3: Name: Germany Race: germany + Side: Allies + Description: Germany: Technology\nSpecial Structure: Advanced Chronosphere\nSpecial Unit: Chrono Tank Country@4: Name: Soviet Race: soviet + Side: Soviet + Description: Soviet: Infantry\nSpecial Ability: Paradrop\nSpecial Ability: Spy Plane Country@5: Name: Russia Race: russia + Side: Soviet + Description: Russia: Tanks\nSpecial Ability: Armor Airdrop\nSpecial Unit: MAD Tank Country@6: Name: Ukraine Race: ukraine + Side: Soviet + Description: Ukraine: Demolitions\nSpecial Ability: Parabombs\nSpecial Unit: Demolition Truck DomainIndex: SmudgeLayer@SCORCH: Type: Scorch