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
if (orderManager.NetFrameNumber == 0)
orderManager.LastTickTime = Environment.TickCount;
world.TickRender(worldRenderer);
viewport.Tick();
}
}

View File

@@ -217,6 +217,7 @@
<Compile Include="World.cs" />
<Compile Include="WorldUtils.cs" />
<Compile Include="Network\ReplayRecorderConnection.cs" />
<Compile Include="Widgets\TooltipContainerWidget.cs" />
</ItemGroup>
<ItemGroup>
<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 ITickRender { void TickRender(WorldRenderer wr, Actor self); }
public interface IRender { IEnumerable<Renderable> Render(Actor self, WorldRenderer wr); }
public interface IAutoSelectionSize { int2 SelectionSize(Actor self); }

View File

@@ -12,31 +12,42 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Network;
namespace OpenRA.Widgets
{
public class MapPreviewWidget : Widget
{
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<int, int2> OnTooltip = (_, __) => { };
public bool IgnoreMouseInput = false;
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)
: base(other)
{
lastMap = other.lastMap;
Map = other.Map;
SpawnColors = other.SpawnColors;
SpawnClients = other.SpawnClients;
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); }
@@ -53,6 +64,18 @@ namespace OpenRA.Widgets
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)
{
var map = Map();
@@ -68,19 +91,30 @@ namespace OpenRA.Widgets
public override void Draw()
{
var map = Map();
if( map == null ) return;
if (map == null)
return;
// Preview unavailable
if (!Loaded)
{
GeneratePreview();
return;
}
if (lastMap != map)
{
lastMap = map;
// Update image data
var preview = PreviewCache[map];
if( mapChooserSheet == null || mapChooserSheet.Size.Width != preview.Width || mapChooserSheet.Size.Height != preview.Height )
mapChooserSheet = new Sheet(new Size( preview.Width, preview.Height ) );
Bitmap preview;
lock (syncRoot)
preview = Previews[map.Uid];
mapChooserSheet.Texture.SetData( preview );
mapChooserSprite = new Sprite( mapChooserSheet, new Rectangle( 0, 0, map.Bounds.Width, map.Bounds.Height ), TextureChannel.Alpha );
if (mapChooserSheet == null || mapChooserSheet.Size.Width != preview.Width || mapChooserSheet.Size.Height != preview.Height)
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
@@ -90,13 +124,14 @@ namespace OpenRA.Widgets
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));
Game.Renderer.RgbaSpriteRenderer.DrawSprite( mapChooserSprite,
Game.Renderer.RgbaSpriteRenderer.DrawSprite(mapChooserSprite,
new float2(MapRect.Location),
new float2( MapRect.Size ) );
new float2(MapRect.Size));
TooltipSpawnIndex = -1;
if (ShowSpawnPoints)
{
var colors = SpawnColors();
var colors = SpawnClients().ToDictionary(c => c.Key, c => c.Value.ColorRamp.GetColor(0));
var spawnPoints = map.GetSpawnPoints().ToList();
foreach (var p in spawnPoints)
@@ -113,21 +148,82 @@ namespace OpenRA.Widgets
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>
/// Forces loading the preview into the map cache.
/// </summary>
public Bitmap LoadMapPreview()
{
var map = Map();
if( map == null ) return null;
// Async map preview generation bits
enum PreviewStatus { Invalid, Uncached, Generating, Cached }
static Thread previewLoaderThread;
static object syncRoot = new object();
static Queue<string> cacheUids = new Queue<string>();
static readonly Dictionary<string, Bitmap> Previews = new Dictionary<string, Bitmap>();
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 string ItemKey;
public ScrollItemWidget()
: base()
{
@@ -28,6 +30,7 @@ namespace OpenRA.Widgets
IsVisible = () => false;
VisualHeight = 0;
IgnoreChildMouseOver = true;
Key = other.Key;
}
public Func<bool> IsSelected = () => false;
@@ -59,5 +62,12 @@ namespace OpenRA.Widgets
w.OnDoubleClick = onDoubleClick;
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.Drawing;
using System.Linq;
using OpenRA.Graphics;
namespace OpenRA.Widgets
@@ -143,6 +144,25 @@ namespace OpenRA.Widgets
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 ()
{
if (UpPressed) Scroll(1);

View File

@@ -0,0 +1,67 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Widgets;
using System;
namespace OpenRA.Widgets
{
public class TooltipContainerWidget : Widget
{
static readonly Action Nothing = () => {};
public int2 CursorOffset = new int2(0, 20);
public Action BeforeRender = Nothing;
public int TooltipDelay = 2;
Widget tooltip;
public TooltipContainerWidget()
{
IsVisible = () => Viewport.TicksSinceLastMove >= TooltipDelay;
}
public void SetTooltip(string id, WidgetArgs args)
{
RemoveTooltip();
tooltip = Ui.LoadWidget(id, this, new WidgetArgs(args) {{ "tooltipContainer", this }});
}
public void RemoveTooltip()
{
RemoveChildren();
BeforeRender = Nothing;
}
public override void Draw() { BeforeRender(); }
public override Rectangle GetEventBounds() { return Rectangle.Empty; }
public override int2 ChildOrigin
{
get
{
var pos = Viewport.LastMousePos + CursorOffset;
if (tooltip != null)
{
if (pos.X + tooltip.Bounds.Right > Game.viewport.Width)
pos.X = Game.viewport.Width - tooltip.Bounds.Right;
}
return pos;
}
}
public override string GetCursor(int2 pos) { return null; }
}
}

View File

@@ -13,6 +13,7 @@ using System.Collections.Generic;
using System.Linq;
using OpenRA.Effects;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Orders;
using OpenRA.Support;
@@ -192,7 +193,12 @@ namespace OpenRA
while (frameEndActions.Count != 0)
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; } }