diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs
index c82f1db633..abceca46dd 100644
--- a/OpenRA.Game/Game.cs
+++ b/OpenRA.Game/Game.cs
@@ -502,6 +502,10 @@ namespace OpenRA
using (new PerfSample("render_widgets"))
{
+ Game.Renderer.WorldVoxelRenderer.BeginFrame();
+ Ui.PrepareRenderables();
+ Game.Renderer.WorldVoxelRenderer.EndFrame();
+
Ui.Draw();
if (ModData != null && ModData.CursorProvider != null)
diff --git a/OpenRA.Game/Graphics/Renderer.cs b/OpenRA.Game/Graphics/Renderer.cs
index 6880fa30df..19211ff5be 100644
--- a/OpenRA.Game/Graphics/Renderer.cs
+++ b/OpenRA.Game/Graphics/Renderer.cs
@@ -108,7 +108,11 @@ namespace OpenRA.Graphics
public void BeginFrame(int2 scroll, float zoom)
{
Device.Clear();
+ SetViewportParams(scroll, zoom);
+ }
+ public void SetViewportParams(int2 scroll, float zoom)
+ {
var resolutionChanged = lastResolution != Resolution;
if (resolutionChanged)
{
diff --git a/OpenRA.Game/Widgets/Widget.cs b/OpenRA.Game/Widgets/Widget.cs
index 081df1efaa..8723bae336 100644
--- a/OpenRA.Game/Widgets/Widget.cs
+++ b/OpenRA.Game/Widgets/Widget.cs
@@ -72,6 +72,8 @@ namespace OpenRA.Widgets
public static void Tick() { Root.TickOuter(); }
+ public static void PrepareRenderables() { Root.PrepareRenderablesOuter(); }
+
public static void Draw() { Root.DrawOuter(); }
public static bool HandleInput(MouseInput mi)
@@ -387,6 +389,18 @@ namespace OpenRA.Widgets
return handled;
}
+ public virtual void PrepareRenderables() { }
+
+ public virtual void PrepareRenderablesOuter()
+ {
+ if (IsVisible())
+ {
+ PrepareRenderables();
+ foreach (var child in Children)
+ child.PrepareRenderablesOuter();
+ }
+ }
+
public virtual void Draw() { }
public virtual void DrawOuter()
diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index e33514ee51..e9c28283cb 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -510,6 +510,7 @@
+
diff --git a/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs b/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs
new file mode 100644
index 0000000000..08eb24e567
--- /dev/null
+++ b/OpenRA.Mods.Common/Widgets/ActorPreviewWidget.cs
@@ -0,0 +1,116 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2015 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.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using OpenRA.Graphics;
+using OpenRA.Mods.Common.Graphics;
+using OpenRA.Mods.Common.Traits;
+using OpenRA.Primitives;
+using OpenRA.Widgets;
+
+namespace OpenRA.Mods.Common.Widgets
+{
+ public class ActorPreviewWidget : Widget
+ {
+ public bool Animate = false;
+ public Func GetScale = () => 1f;
+
+ readonly WorldRenderer worldRenderer;
+
+ IActorPreview[] preview = new IActorPreview[0];
+ public int2 PreviewOffset { get; private set; }
+ public int2 IdealPreviewSize { get; private set; }
+
+ [ObjectCreator.UseCtor]
+ public ActorPreviewWidget(WorldRenderer worldRenderer)
+ {
+ this.worldRenderer = worldRenderer;
+ }
+
+ protected ActorPreviewWidget(ActorPreviewWidget other)
+ : base(other)
+ {
+ preview = other.preview;
+ worldRenderer = other.worldRenderer;
+ }
+
+ public override Widget Clone() { return new ActorPreviewWidget(this); }
+
+ public void SetPreview(ActorInfo actor, Player owner, TypeDictionary td)
+ {
+ var init = new ActorPreviewInitializer(actor, owner, worldRenderer, td);
+ preview = actor.Traits.WithInterface()
+ .SelectMany(rpi => rpi.RenderPreview(init))
+ .ToArray();
+
+ // Calculate the preview bounds
+ PreviewOffset = int2.Zero;
+ IdealPreviewSize = int2.Zero;
+
+ var r = preview
+ .SelectMany(p => p.Render(worldRenderer, WPos.Zero))
+ .OrderBy(WorldRenderer.RenderableScreenZPositionComparisonKey)
+ .Select(rr => rr.PrepareRender(worldRenderer));
+
+ if (r.Any())
+ {
+ var b = r.First().ScreenBounds(worldRenderer);
+ foreach (var rr in r.Skip(1))
+ b = Rectangle.Union(b, rr.ScreenBounds(worldRenderer));
+
+ IdealPreviewSize = new int2(b.Width, b.Height);
+ PreviewOffset = -new int2(b.Left, b.Top) - IdealPreviewSize / 2;
+ }
+ }
+
+ IFinalizedRenderable[] renderables;
+ public override void PrepareRenderables()
+ {
+ renderables = preview
+ .SelectMany(p => p.Render(worldRenderer, WPos.Zero))
+ .OrderBy(WorldRenderer.RenderableScreenZPositionComparisonKey)
+ .Select(r => r.PrepareRender(worldRenderer))
+ .ToArray();
+ }
+
+ public override void Draw()
+ {
+ // HACK: The split between world and UI shaders is a giant PITA because it isn't
+ // feasible to maintain two parallel sets of renderables for the two cases.
+ // Instead, we temporarily hijack the world rendering context and set the position
+ // and zoom values to give the desired screen position and size.
+ var scale = GetScale();
+ var origin = RenderOrigin + new int2(RenderBounds.Size.Width / 2, RenderBounds.Size.Height / 2);
+
+ // The scale affects world -> screen transform, which we don't want when drawing the (fixed) UI.
+ if (scale != 1f)
+ origin = (1f / scale * origin.ToFloat2()).ToInt2();
+
+ Game.Renderer.Flush();
+ Game.Renderer.SetViewportParams(-origin - PreviewOffset, scale);
+
+ foreach (var r in renderables)
+ r.Render(worldRenderer);
+
+ Game.Renderer.Flush();
+ Game.Renderer.SetViewportParams(worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.Zoom);
+ }
+
+ public override void Tick()
+ {
+ if (Animate)
+ foreach (var p in preview)
+ p.Tick();
+ }
+ }
+}