Merge pull request #2968 from pchote/map-chooser

Map chooser polish & refactoring
This commit is contained in:
Matthias Mailänder
2013-04-06 01:08:21 -07:00
18 changed files with 382 additions and 88 deletions

View File

@@ -190,7 +190,8 @@ namespace OpenRA
else else
if (orderManager.NetFrameNumber == 0) if (orderManager.NetFrameNumber == 0)
orderManager.LastTickTime = Environment.TickCount; orderManager.LastTickTime = Environment.TickCount;
world.TickRender(worldRenderer);
viewport.Tick(); viewport.Tick();
} }
} }

View File

@@ -217,6 +217,7 @@
<Compile Include="World.cs" /> <Compile Include="World.cs" />
<Compile Include="WorldUtils.cs" /> <Compile Include="WorldUtils.cs" />
<Compile Include="Network\ReplayRecorderConnection.cs" /> <Compile Include="Network\ReplayRecorderConnection.cs" />
<Compile Include="Widgets\TooltipContainerWidget.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj"> <ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -34,6 +34,7 @@ namespace OpenRA.Traits
} }
public interface ITick { void Tick(Actor self); } public interface ITick { void Tick(Actor self); }
public interface ITickRender { void TickRender(WorldRenderer wr, Actor self); }
public interface IRender { IEnumerable<Renderable> Render(Actor self, WorldRenderer wr); } public interface IRender { IEnumerable<Renderable> Render(Actor self, WorldRenderer wr); }
public interface IAutoSelectionSize { int2 SelectionSize(Actor self); } public interface IAutoSelectionSize { int2 SelectionSize(Actor self); }

View File

@@ -12,31 +12,42 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Threading;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Network;
namespace OpenRA.Widgets namespace OpenRA.Widgets
{ {
public class MapPreviewWidget : Widget public class MapPreviewWidget : Widget
{ {
public Func<Map> Map = () => null; public Func<Map> Map = () => null;
public Func<Dictionary<int2, Color>> SpawnColors = () => new Dictionary<int2, Color>(); public Func<Dictionary<int2, Session.Client>> SpawnClients = () => new Dictionary<int2, Session.Client>();
public Action<MouseInput> OnMouseDown = _ => {}; public Action<MouseInput> OnMouseDown = _ => {};
public Action<int, int2> OnTooltip = (_, __) => { }; public Action<int, int2> OnTooltip = (_, __) => { };
public bool IgnoreMouseInput = false; public bool IgnoreMouseInput = false;
public bool ShowSpawnPoints = true; public bool ShowSpawnPoints = true;
static readonly Cache<Map,Bitmap> PreviewCache = new Cache<Map, Bitmap>(stub => Minimap.RenderMapPreview( new Map( stub.Path ))); public readonly string TooltipContainer;
public readonly string TooltipTemplate = "SPAWN_TOOLTIP";
Lazy<TooltipContainerWidget> tooltipContainer;
public int TooltipSpawnIndex = -1;
public MapPreviewWidget() : base() { } public MapPreviewWidget() : base()
{
tooltipContainer = Lazy.New(() => Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
}
protected MapPreviewWidget(MapPreviewWidget other) protected MapPreviewWidget(MapPreviewWidget other)
: base(other) : base(other)
{ {
lastMap = other.lastMap; lastMap = other.lastMap;
Map = other.Map; Map = other.Map;
SpawnColors = other.SpawnColors; SpawnClients = other.SpawnClients;
ShowSpawnPoints = other.ShowSpawnPoints; ShowSpawnPoints = other.ShowSpawnPoints;
TooltipTemplate = other.TooltipTemplate;
TooltipContainer = other.TooltipContainer;
tooltipContainer = Lazy.New(() => Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
} }
public override Widget Clone() { return new MapPreviewWidget(this); } public override Widget Clone() { return new MapPreviewWidget(this); }
@@ -53,6 +64,18 @@ namespace OpenRA.Widgets
return true; return true;
} }
public override void MouseEntered()
{
if (TooltipContainer == null) return;
tooltipContainer.Value.SetTooltip(TooltipTemplate, new WidgetArgs() {{ "preview", this }});
}
public override void MouseExited()
{
if (TooltipContainer == null) return;
tooltipContainer.Value.RemoveTooltip();
}
public int2 ConvertToPreview(int2 point) public int2 ConvertToPreview(int2 point)
{ {
var map = Map(); var map = Map();
@@ -68,19 +91,30 @@ namespace OpenRA.Widgets
public override void Draw() public override void Draw()
{ {
var map = Map(); var map = Map();
if( map == null ) return; if (map == null)
return;
// Preview unavailable
if (!Loaded)
{
GeneratePreview();
return;
}
if (lastMap != map) if (lastMap != map)
{ {
lastMap = map; lastMap = map;
// Update image data // Update image data
var preview = PreviewCache[map]; Bitmap preview;
if( mapChooserSheet == null || mapChooserSheet.Size.Width != preview.Width || mapChooserSheet.Size.Height != preview.Height ) lock (syncRoot)
mapChooserSheet = new Sheet(new Size( preview.Width, preview.Height ) ); preview = Previews[map.Uid];
mapChooserSheet.Texture.SetData( preview ); if (mapChooserSheet == null || mapChooserSheet.Size.Width != preview.Width || mapChooserSheet.Size.Height != preview.Height)
mapChooserSprite = new Sprite( mapChooserSheet, new Rectangle( 0, 0, map.Bounds.Width, map.Bounds.Height ), TextureChannel.Alpha ); mapChooserSheet = new Sheet(new Size(preview.Width, preview.Height));
mapChooserSheet.Texture.SetData(preview);
mapChooserSprite = new Sprite(mapChooserSheet, new Rectangle(0, 0, map.Bounds.Width, map.Bounds.Height), TextureChannel.Alpha);
} }
// Update map rect // Update map rect
@@ -90,13 +124,14 @@ namespace OpenRA.Widgets
var dh = (int)(PreviewScale * (size - map.Bounds.Height)) / 2; var dh = (int)(PreviewScale * (size - map.Bounds.Height)) / 2;
MapRect = new Rectangle(RenderBounds.X + dw, RenderBounds.Y + dh, (int)(map.Bounds.Width * PreviewScale), (int)(map.Bounds.Height * PreviewScale)); MapRect = new Rectangle(RenderBounds.X + dw, RenderBounds.Y + dh, (int)(map.Bounds.Width * PreviewScale), (int)(map.Bounds.Height * PreviewScale));
Game.Renderer.RgbaSpriteRenderer.DrawSprite( mapChooserSprite, Game.Renderer.RgbaSpriteRenderer.DrawSprite(mapChooserSprite,
new float2(MapRect.Location), new float2(MapRect.Location),
new float2( MapRect.Size ) ); new float2(MapRect.Size));
TooltipSpawnIndex = -1;
if (ShowSpawnPoints) if (ShowSpawnPoints)
{ {
var colors = SpawnColors(); var colors = SpawnClients().ToDictionary(c => c.Key, c => c.Value.ColorRamp.GetColor(0));
var spawnPoints = map.GetSpawnPoints().ToList(); var spawnPoints = map.GetSpawnPoints().ToList();
foreach (var p in spawnPoints) foreach (var p in spawnPoints)
@@ -113,21 +148,82 @@ namespace OpenRA.Widgets
if ((pos - Viewport.LastMousePos).LengthSquared < 64) if ((pos - Viewport.LastMousePos).LengthSquared < 64)
{ {
OnTooltip(spawnPoints.IndexOf(p) + 1, pos); TooltipSpawnIndex = spawnPoints.IndexOf(p) + 1;
// Legacy tooltip behavior
if (TooltipContainer == null)
OnTooltip(TooltipSpawnIndex, pos);
} }
} }
} }
} }
/// <summary> // Async map preview generation bits
/// Forces loading the preview into the map cache. enum PreviewStatus { Invalid, Uncached, Generating, Cached }
/// </summary> static Thread previewLoaderThread;
public Bitmap LoadMapPreview() static object syncRoot = new object();
{ static Queue<string> cacheUids = new Queue<string>();
var map = Map(); static readonly Dictionary<string, Bitmap> Previews = new Dictionary<string, Bitmap>();
if( map == null ) return null;
return PreviewCache[map]; void LoadAsyncInternal()
{
for (;;)
{
string uid;
lock (syncRoot)
{
if (cacheUids.Count == 0)
break;
uid = cacheUids.Peek();
}
var bitmap = Minimap.RenderMapPreview(Game.modData.AvailableMaps[uid]);
lock (syncRoot)
{
// TODO: We should add previews to a sheet here (with multiple previews per sheet)
Previews.Add(uid, bitmap);
cacheUids.Dequeue();
}
// Yuck... But this helps the UI Jank when opening the map selector significantly.
Thread.Sleep(50);
}
} }
void GeneratePreview()
{
var m = Map();
if (m == null)
return;
var status = Status(m);
if (status == PreviewStatus.Uncached)
lock (syncRoot)
cacheUids.Enqueue(m.Uid);
if (previewLoaderThread == null || !previewLoaderThread.IsAlive)
{
previewLoaderThread = new Thread(LoadAsyncInternal);
previewLoaderThread.Start();
}
}
static PreviewStatus Status(Map m)
{
if (m == null)
return PreviewStatus.Invalid;
lock (syncRoot)
{
if (Previews.ContainsKey(m.Uid))
return PreviewStatus.Cached;
if (cacheUids.Contains(m.Uid))
return PreviewStatus.Generating;
}
return PreviewStatus.Uncached;
}
public bool Loaded { get { return Status(Map()) == PreviewStatus.Cached; } }
} }
} }

View File

@@ -14,6 +14,8 @@ namespace OpenRA.Widgets
{ {
public class ScrollItemWidget : ButtonWidget public class ScrollItemWidget : ButtonWidget
{ {
public string ItemKey;
public ScrollItemWidget() public ScrollItemWidget()
: base() : base()
{ {
@@ -28,6 +30,7 @@ namespace OpenRA.Widgets
IsVisible = () => false; IsVisible = () => false;
VisualHeight = 0; VisualHeight = 0;
IgnoreChildMouseOver = true; IgnoreChildMouseOver = true;
Key = other.Key;
} }
public Func<bool> IsSelected = () => false; public Func<bool> IsSelected = () => false;
@@ -59,5 +62,12 @@ namespace OpenRA.Widgets
w.OnDoubleClick = onDoubleClick; w.OnDoubleClick = onDoubleClick;
return w; return w;
} }
public static ScrollItemWidget Setup(string key, ScrollItemWidget template, Func<bool> isSelected, Action onClick)
{
var w = Setup(template, isSelected, onClick);
w.ItemKey = key;
return w;
}
} }
} }

View File

@@ -10,6 +10,7 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Linq;
using OpenRA.Graphics; using OpenRA.Graphics;
namespace OpenRA.Widgets namespace OpenRA.Widgets
@@ -143,6 +144,25 @@ namespace OpenRA.Widgets
ListOffset = 0; ListOffset = 0;
} }
public void ScrollToItem(string itemKey)
{
var item = Children.FirstOrDefault(c =>
{
var si = c as ScrollItemWidget;
return si != null && si.ItemKey == itemKey;
});
if (item == null)
return;
// Scroll the item to be visible
if (item.Bounds.Top + ListOffset < 0)
ListOffset = ItemSpacing - item.Bounds.Top;
if (item.Bounds.Bottom + ListOffset > RenderBounds.Height)
ListOffset = RenderBounds.Height - item.Bounds.Bottom - ItemSpacing;
}
public override void Tick () public override void Tick ()
{ {
if (UpPressed) Scroll(1); if (UpPressed) Scroll(1);

View File

@@ -13,11 +13,10 @@ using System.Drawing;
using System.Linq; using System.Linq;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.RA;
using OpenRA.Widgets; using OpenRA.Widgets;
using System; using System;
namespace OpenRA.Mods.Cnc.Widgets namespace OpenRA.Widgets
{ {
public class TooltipContainerWidget : Widget public class TooltipContainerWidget : Widget
{ {

View File

@@ -13,6 +13,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Effects; using OpenRA.Effects;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Network; using OpenRA.Network;
using OpenRA.Orders; using OpenRA.Orders;
using OpenRA.Support; using OpenRA.Support;
@@ -192,7 +193,12 @@ namespace OpenRA
while (frameEndActions.Count != 0) while (frameEndActions.Count != 0)
frameEndActions.Dequeue()(this); frameEndActions.Dequeue()(this);
}
// For things that want to update their render state once per tick, ignoring pause state
public void TickRender(WorldRenderer wr)
{
ActorsWithTrait<ITickRender>().Do(x => x.Trait.TickRender(wr, x.Actor));
} }
public IEnumerable<Actor> Actors { get { return actors; } } public IEnumerable<Actor> Actors { get { return actors; } }

View File

@@ -11,6 +11,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Traits; using OpenRA.Traits;
namespace OpenRA.Mods.Cnc namespace OpenRA.Mods.Cnc
@@ -22,7 +23,7 @@ namespace OpenRA.Mods.Cnc
public object Create(ActorInitializer init) { return new CncMenuPaletteEffect(this); } public object Create(ActorInitializer init) { return new CncMenuPaletteEffect(this); }
} }
public class CncMenuPaletteEffect : IPaletteModifier, ITick public class CncMenuPaletteEffect : IPaletteModifier, ITickRender
{ {
public enum EffectType { None, Black, Desaturated } public enum EffectType { None, Black, Desaturated }
public readonly CncMenuPaletteEffectInfo Info; public readonly CncMenuPaletteEffectInfo Info;
@@ -40,7 +41,7 @@ namespace OpenRA.Mods.Cnc
to = type; to = type;
} }
public void Tick(Actor self) public void TickRender(WorldRenderer wr, Actor self)
{ {
if (remainingFrames > 0) if (remainingFrames > 0)
remainingFrames--; remainingFrames--;

View File

@@ -111,10 +111,10 @@
<Compile Include="Widgets\ProductionTabsWidget.cs" /> <Compile Include="Widgets\ProductionTabsWidget.cs" />
<Compile Include="Widgets\SupportPowersWidget.cs" /> <Compile Include="Widgets\SupportPowersWidget.cs" />
<Compile Include="Widgets\ToggleButtonWidget.cs" /> <Compile Include="Widgets\ToggleButtonWidget.cs" />
<Compile Include="Widgets\TooltipContainerWidget.cs" />
<Compile Include="WithFire.cs" /> <Compile Include="WithFire.cs" />
<Compile Include="WithRoof.cs" /> <Compile Include="WithRoof.cs" />
<Compile Include="Widgets\ResourceBarWidget.cs" /> <Compile Include="Widgets\ResourceBarWidget.cs" />
<Compile Include="Widgets\Logic\SpawnSelectorTooltipLogic.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj"> <ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -0,0 +1,79 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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.Drawing;
using System.Linq;
using OpenRA.Widgets;
using OpenRA.Network;
namespace OpenRA.Mods.Cnc.Widgets.Logic
{
public class SpawnSelectorTooltipLogic
{
[ObjectCreator.UseCtor]
public SpawnSelectorTooltipLogic(Widget widget, TooltipContainerWidget tooltipContainer, MapPreviewWidget preview)
{
widget.IsVisible = () => preview.TooltipSpawnIndex != -1;
var label = widget.Get<LabelWidget>("LABEL");
var flag = widget.Get<ImageWidget>("FLAG");
var team = widget.Get<LabelWidget>("TEAM");
var ownerFont = Game.Renderer.Fonts[label.Font];
var teamFont = Game.Renderer.Fonts[team.Font];
var cachedWidth = 0;
var labelText = "";
string playerCountry = null;
var playerTeam = -1;
tooltipContainer.BeforeRender = () =>
{
var client = preview.SpawnClients().Values.FirstOrDefault(c => c.SpawnPoint == preview.TooltipSpawnIndex);
var teamWidth = 0;
if (client == null)
{
labelText = "Available spawn";
playerCountry = null;
playerTeam = 0;
widget.Bounds.Height = 25;
}
else
{
labelText = client.Name;
playerCountry = client.Country;
playerTeam = client.Team;
widget.Bounds.Height = playerTeam > 0 ? 40 : 25;
teamWidth = teamFont.Measure(team.GetText()).X;
}
label.Bounds.X = playerCountry != null ? flag.Bounds.Right + 5 : 5;
var textWidth = ownerFont.Measure(labelText).X;
if (textWidth != cachedWidth)
{
label.Bounds.Width = textWidth;
widget.Bounds.Width = 2*label.Bounds.X + textWidth;
}
widget.Bounds.Width = Math.Max(teamWidth + 10, label.Bounds.Right + 5);
team.Bounds.Width = widget.Bounds.Width;
};
label.GetText = () => labelText;
flag.IsVisible = () => playerCountry != null;
flag.GetImageCollection = () => "flags";
flag.GetImageName = () => playerCountry;
team.GetText = () => "Team {0}".F(playerTeam);
team.IsVisible = () => playerTeam > 0;
}
}
}

View File

@@ -113,7 +113,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
mapPreview.Map = () => Map; mapPreview.Map = () => Map;
mapPreview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint( orderManager, mapPreview, Map, mi ); mapPreview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint( orderManager, mapPreview, Map, mi );
mapPreview.OnTooltip = (spawnPoint, pos) => LobbyUtils.ShowSpawnPointTooltip(orderManager, spawnPoint, pos); mapPreview.OnTooltip = (spawnPoint, pos) => LobbyUtils.ShowSpawnPointTooltip(orderManager, spawnPoint, pos);
mapPreview.SpawnColors = () => LobbyUtils.GetSpawnColors(orderManager, Map); mapPreview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager, Map);
var mapTitle = lobby.GetOrNull<LabelWidget>("MAP_TITLE"); var mapTitle = lobby.GetOrNull<LabelWidget>("MAP_TITLE");
if (mapTitle != null) if (mapTitle != null)
@@ -122,6 +122,20 @@ namespace OpenRA.Mods.RA.Widgets.Logic
mapTitle.GetText = () => Map.Title; mapTitle.GetText = () => Map.Title;
} }
var mapType = lobby.GetOrNull<LabelWidget>("MAP_TYPE");
if (mapType != null)
{
mapType.IsVisible = () => Map != null;
mapType.GetText = () => Map.Type;
}
var mapAuthor = lobby.GetOrNull<LabelWidget>("MAP_AUTHOR");
if (mapAuthor != null)
{
mapAuthor.IsVisible = () => Map != null;
mapAuthor.GetText = () => "Created by {0}".F(Map.Author);
}
CountryNames = Rules.Info["world"].Traits.WithInterface<CountryInfo>() CountryNames = Rules.Info["world"].Traits.WithInterface<CountryInfo>()
.Where(c => c.Selectable) .Where(c => c.Selectable)
.ToDictionary(a => a.Race, a => a.Name); .ToDictionary(a => a.Race, a => a.Name);

View File

@@ -150,14 +150,14 @@ namespace OpenRA.Mods.RA.Widgets.Logic
color.AttachPanel(colorChooser); color.AttachPanel(colorChooser);
} }
public static Dictionary<int2, Color> GetSpawnColors(OrderManager orderManager, Map map) public static Dictionary<int2, Session.Client> GetSpawnClients(OrderManager orderManager, Map map)
{ {
var spawns = map.GetSpawnPoints(); var spawns = map.GetSpawnPoints();
return orderManager.LobbyInfo.Clients return orderManager.LobbyInfo.Clients
.Where( c => c.SpawnPoint != 0) .Where(c => c.SpawnPoint != 0)
.ToDictionary( .ToDictionary(
c => spawns[c.SpawnPoint - 1], c => spawns[c.SpawnPoint - 1],
c => c.ColorRamp.GetColor(0)); c => c);
} }
public static void SelectSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, Map map, MouseInput mi) public static void SelectSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, Map map, MouseInput mi)

View File

@@ -9,6 +9,7 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using OpenRA.FileFormats; using OpenRA.FileFormats;
@@ -19,10 +20,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic
public class MapChooserLogic public class MapChooserLogic
{ {
Map map; Map map;
// May be a subset of available maps if a mode filter is active
Dictionary<string, Map> visibleMaps;
ScrollPanelWidget scrollpanel; ScrollPanelWidget scrollpanel;
ScrollItemWidget itemTemplate; ScrollItemWidget itemTemplate;
string gameMode; string gameMode;
Thread mapLoaderThread;
[ObjectCreator.UseCtor] [ObjectCreator.UseCtor]
internal MapChooserLogic(Widget widget, string initialMap, Action onExit, Action<Map> onSelect) internal MapChooserLogic(Widget widget, string initialMap, Action onExit, Action<Map> onSelect)
@@ -34,6 +38,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
scrollpanel = widget.Get<ScrollPanelWidget>("MAP_LIST"); scrollpanel = widget.Get<ScrollPanelWidget>("MAP_LIST");
scrollpanel.ScrollVelocity = 40f; scrollpanel.ScrollVelocity = 40f;
scrollpanel.Layout = new GridLayout(scrollpanel);
itemTemplate = scrollpanel.Get<ScrollItemWidget>("MAP_TEMPLATE"); itemTemplate = scrollpanel.Get<ScrollItemWidget>("MAP_TEMPLATE");
@@ -55,7 +60,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
{ {
var item = ScrollItemWidget.Setup(template, var item = ScrollItemWidget.Setup(template,
() => gameMode == ii.First, () => gameMode == ii.First,
() => { gameMode = ii.First; EnumerateMapsAsync(); }); () => { gameMode = ii.First; EnumerateMaps(); });
item.Get<LabelWidget>("LABEL").GetText = () => showItem(ii); item.Get<LabelWidget>("LABEL").GetText = () => showItem(ii);
return item; return item;
}; };
@@ -66,34 +71,34 @@ namespace OpenRA.Mods.RA.Widgets.Logic
gameModeDropdown.GetText = () => showItem(gameModes.First(m => m.First == gameMode)); gameModeDropdown.GetText = () => showItem(gameModes.First(m => m.First == gameMode));
} }
EnumerateMapsAsync(); var randomMapButton = widget.GetOrNull<ButtonWidget>("RANDOMMAP_BUTTON");
} if (randomMapButton != null)
{
randomMapButton.OnClick = () =>
{
var kv = visibleMaps.Random(Game.CosmeticRandom);
map = kv.Value;
scrollpanel.ScrollToItem(kv.Key);
};
randomMapButton.IsDisabled = () => visibleMaps == null || visibleMaps.Count == 0;
}
void EnumerateMapsAsync() EnumerateMaps();
{
if (mapLoaderThread != null && mapLoaderThread.IsAlive)
mapLoaderThread.Abort(); // violent, but should be fine since we are not doing anything sensitive in this thread
mapLoaderThread = new Thread(EnumerateMaps);
mapLoaderThread.Start();
} }
void EnumerateMaps() void EnumerateMaps()
{ {
Game.RunAfterTick(() => scrollpanel.RemoveChildren()); // queue removal in case another thread added any items to the game queue
scrollpanel.Layout = new GridLayout(scrollpanel);
scrollpanel.ScrollToTop();
var maps = Game.modData.AvailableMaps var maps = Game.modData.AvailableMaps
.Where(kv => kv.Value.Selectable) .Where(kv => kv.Value.Selectable)
.Where(kv => kv.Value.Type == gameMode || gameMode == null) .Where(kv => kv.Value.Type == gameMode || gameMode == null)
.OrderBy(kv => kv.Value.PlayerCount) .OrderBy(kv => kv.Value.PlayerCount)
.ThenBy(kv => kv.Value.Title); .ThenBy(kv => kv.Value.Title);
scrollpanel.RemoveChildren();
foreach (var kv in maps) foreach (var kv in maps)
{ {
var m = kv.Value; var m = kv.Value;
var item = ScrollItemWidget.Setup(itemTemplate, () => m == map, () => map = m); var item = ScrollItemWidget.Setup(kv.Key, itemTemplate, () => m == map, () => map = m);
var titleLabel = item.Get<LabelWidget>("TITLE"); var titleLabel = item.Get<LabelWidget>("TITLE");
titleLabel.GetText = () => m.Title; titleLabel.GetText = () => m.Title;
@@ -102,17 +107,21 @@ namespace OpenRA.Mods.RA.Widgets.Logic
previewWidget.IgnoreMouseOver = true; previewWidget.IgnoreMouseOver = true;
previewWidget.IgnoreMouseInput = true; previewWidget.IgnoreMouseInput = true;
previewWidget.Map = () => m; previewWidget.Map = () => m;
previewWidget.LoadMapPreview(); previewWidget.IsVisible = () => previewWidget.RenderBounds.IntersectsWith(scrollpanel.RenderBounds);
var detailsWidget = item.Get<LabelWidget>("DETAILS"); var previewLoadingWidget = item.GetOrNull<BackgroundWidget>("PREVIEW_PLACEHOLDER");
if (previewLoadingWidget != null)
previewLoadingWidget.IsVisible = () => !previewWidget.Loaded;
var detailsWidget = item.GetOrNull<LabelWidget>("DETAILS");
if (detailsWidget != null) if (detailsWidget != null)
detailsWidget.GetText = () => "{0} ({1})".F(m.Type, m.PlayerCount); detailsWidget.GetText = () => "{0} ({1} players)".F(m.Type, m.PlayerCount);
var authorWidget = item.Get<LabelWidget>("AUTHOR"); var authorWidget = item.GetOrNull<LabelWidget>("AUTHOR");
if (authorWidget != null) if (authorWidget != null)
authorWidget.GetText = () => m.Author; authorWidget.GetText = () => "Created by {0}".F(m.Author);
var sizeWidget = item.Get<LabelWidget>("SIZE"); var sizeWidget = item.GetOrNull<LabelWidget>("SIZE");
if (sizeWidget != null) if (sizeWidget != null)
{ {
var size = m.Bounds.Width + "x" + m.Bounds.Height; var size = m.Bounds.Width + "x" + m.Bounds.Height;
@@ -124,8 +133,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic
sizeWidget.GetText = () => size; sizeWidget.GetText = () => size;
} }
Game.RunAfterTick(() => scrollpanel.AddChild(item)); scrollpanel.AddChild(item);
} }
visibleMaps = maps.ToDictionary(kv => kv.Key, kv => kv.Value);
if (visibleMaps.ContainsValue(map))
scrollpanel.ScrollToItem(visibleMaps.First(m => m.Value == map).Key);
} }
} }
} }

View File

@@ -32,13 +32,28 @@ Container@SERVER_LOBBY:
Y:1 Y:1
Width:192 Width:192
Height:192 Height:192
TooltipContainer:TOOLTIP_CONTAINER
Label@MAP_TITLE: Label@MAP_TITLE:
X:PARENT_RIGHT-15-WIDTH X:PARENT_RIGHT-15-WIDTH
Y:225 Y:227
Width:194 Width:194
Height:25 Height:25
Font:Bold Font:Bold
Align:Center Align:Center
Label@MAP_TYPE:
X:PARENT_RIGHT-15-WIDTH
Y:242
Width:194
Height:25
Font:TinyBold
Align:Center
Label@MAP_AUTHOR:
X:PARENT_RIGHT-15-WIDTH
Y:255
Width:194
Height:25
Font:Tiny
Align:Center
ScrollPanel@PLAYERS: ScrollPanel@PLAYERS:
X:15 X:15
Y:30 Y:30
@@ -329,13 +344,13 @@ Container@SERVER_LOBBY:
Font:Bold Font:Bold
Checkbox@ALLOWCHEATS_CHECKBOX: Checkbox@ALLOWCHEATS_CHECKBOX:
X:15 X:15
Y:255 Y:257
Width:130 Width:130
Height:20 Height:20
Text: Allow Cheats Text: Allow Cheats
Checkbox@CRATES_CHECKBOX: Checkbox@CRATES_CHECKBOX:
X:160 X:160
Y:255 Y:257
Width:80 Width:80
Height:20 Height:20
Text: Crates Text: Crates
@@ -347,13 +362,6 @@ Container@SERVER_LOBBY:
Font:Bold Font:Bold
Visible:false Visible:false
Text:Assign Text:Assign
Button@RANDOMMAP_BUTTON:
X:PARENT_RIGHT-120-15-40
Y:255
Width:120
Height:25
Text:Random Map
Font:Bold
ScrollPanel@CHAT_DISPLAY: ScrollPanel@CHAT_DISPLAY:
X:15 X:15
Y:285 Y:285
@@ -396,6 +404,7 @@ Container@SERVER_LOBBY:
Height:25 Height:25
Align:Right Align:Right
Text:Chat: Text:Chat:
TooltipContainer@TOOLTIP_CONTAINER:
Button@DISCONNECT_BUTTON: Button@DISCONNECT_BUTTON:
X:0 X:0
Y:499 Y:499

View File

@@ -6,62 +6,78 @@ Container@MAPCHOOSER_PANEL:
Height:535 Height:535
Children: Children:
Label@TITLE: Label@TITLE:
Width:790 Width:PARENT_RIGHT
Y:0-25 Y:0-25
Font:BigBold Font:BigBold
Contrast:true Contrast:true
Align:Center Align:Center
Text: Select Map Text: Select Map
Background@bg: Background@bg:
Width:790 Width:PARENT_RIGHT
Height:500 Height:500
Background:panel-black Background:panel-black
Children: Children:
Label@GAMEMODE_DESC:
X:PARENT_RIGHT - WIDTH - 220
Y:10
Width:200
Height:25
Font: Bold
Align: Right
Text:Filter:
DropDownButton@GAMEMODE_FILTER:
X:PARENT_RIGHT - WIDTH - 15
Y:10
Width:200
Height:25
ScrollPanel@MAP_LIST: ScrollPanel@MAP_LIST:
X:15 X:15
Y:30 Y:45
Width:PARENT_RIGHT - 30 Width:PARENT_RIGHT - 30
Height:455 Height:440
Children: Children:
ScrollItem@MAP_TEMPLATE: ScrollItem@MAP_TEMPLATE:
Width:180 Width:168
Height:208 Height:217
X:2 X:2
Y:0 Y:0
Visible:false Visible:false
Children: Children:
Background@PREVIEW_PLACEHOLDER:
X:(PARENT_RIGHT - WIDTH)/2
Y:4
Width:158
Height:158
Background:panel-black
ClickThrough: false
MapPreview@PREVIEW:
X:(PARENT_RIGHT - WIDTH)/2
Y:4
Width:158
Height:158
Label@TITLE: Label@TITLE:
X:2 X:2
Y:PARENT_BOTTOM-47 Y:PARENT_BOTTOM-48
Width:PARENT_RIGHT-4 Width:PARENT_RIGHT-4
Height:25
Align:Center Align:Center
Label@DETAILS: Label@DETAILS:
Width:PARENT_RIGHT-4 Width:PARENT_RIGHT-4
X:2 X:2
Y:PARENT_BOTTOM-35 Y:PARENT_BOTTOM-34
Align:Center Align:Center
Height:25
Font:Tiny Font:Tiny
Label@AUTHOR: Label@AUTHOR:
Width:PARENT_RIGHT-4 Width:PARENT_RIGHT-4
X:2 X:2
Y:PARENT_BOTTOM-26 Y:PARENT_BOTTOM-22
Align:Center Align:Center
Height:25
Font:Tiny Font:Tiny
Label@SIZE: Label@SIZE:
Width:PARENT_RIGHT-4 Width:PARENT_RIGHT-4
X:2 X:2
Y:PARENT_BOTTOM-17 Y:PARENT_BOTTOM-10
Align:Center Align:Center
Height:25
Font:Tiny Font:Tiny
MapPreview@PREVIEW:
X:(PARENT_RIGHT - WIDTH)/2
Y:4
Width:160
Height:160
Button@BUTTON_CANCEL: Button@BUTTON_CANCEL:
Key:escape Key:escape
X:0 X:0
@@ -69,9 +85,16 @@ Container@MAPCHOOSER_PANEL:
Width:140 Width:140
Height:35 Height:35
Text:Cancel Text:Cancel
Button@RANDOMMAP_BUTTON:
Key:space
X:PARENT_RIGHT - 150 - WIDTH
Y:499
Width:140
Height:35
Text:Random
Button@BUTTON_OK: Button@BUTTON_OK:
Key:return Key:return
X:790 - WIDTH X:PARENT_RIGHT - WIDTH
Y:499 Y:499
Width:140 Width:140
Height:35 Height:35

View File

@@ -99,3 +99,23 @@ Background@SUPPORT_POWER_TOOLTIP:
Y:20 Y:20
Font:TinyBold Font:TinyBold
VAlign:Top VAlign:Top
Background@SPAWN_TOOLTIP:
Logic:SpawnSelectorTooltipLogic
Background:panel-black
Width:141
Children:
Label@LABEL:
X:5
Height:23
Font:Bold
Image@FLAG:
X:5
Y:5
Width:32
Height:16
Label@TEAM:
Y:21
Height:15
Font:TinyBold
Align:center

View File

@@ -230,5 +230,6 @@ PVICE:
DrawLineToTarget: DrawLineToTarget:
Selectable: Selectable:
Voice: DinoVoice Voice: DinoVoice
SelectionDecorations:
ActorLostNotification: ActorLostNotification:
Notification: unitlost.aud Notification: unitlost.aud