Added Undo Redo to editor

This commit is contained in:
teinarss
2019-07-13 18:21:01 +02:00
committed by abcdefg30
parent 1f78b3a425
commit 76034c198e
19 changed files with 1155 additions and 164 deletions

View File

@@ -24,6 +24,7 @@ namespace OpenRA.Mods.Common.Widgets
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
readonly World world; readonly World world;
readonly EditorActorLayer editorLayer; readonly EditorActorLayer editorLayer;
readonly EditorActionManager editorActionManager;
readonly EditorViewportControllerWidget editorWidget; readonly EditorViewportControllerWidget editorWidget;
readonly ActorPreviewWidget preview; readonly ActorPreviewWidget preview;
readonly WVec centerOffset; readonly WVec centerOffset;
@@ -38,6 +39,7 @@ namespace OpenRA.Mods.Common.Widgets
worldRenderer = wr; worldRenderer = wr;
world = wr.World; world = wr.World;
editorLayer = world.WorldActor.Trait<EditorActorLayer>(); editorLayer = world.WorldActor.Trait<EditorActorLayer>();
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
Actor = actor; Actor = actor;
this.owner = owner; this.owner = owner;
@@ -101,33 +103,8 @@ namespace OpenRA.Mods.Common.Widgets
return true; return true;
// Enforce first entry of ValidOwnerNames as owner if the actor has RequiresSpecificOwners // Enforce first entry of ValidOwnerNames as owner if the actor has RequiresSpecificOwners
var ownerName = owner.Name; var action = new AddActorAction(editorLayer, Actor, cell, owner, facing);
var specificOwnerInfo = Actor.TraitInfoOrDefault<RequiresSpecificOwnersInfo>(); editorActionManager.Add(action);
if (specificOwnerInfo != null && !specificOwnerInfo.ValidOwnerNames.Contains(ownerName))
ownerName = specificOwnerInfo.ValidOwnerNames.First();
var newActorReference = new ActorReference(Actor.Name);
newActorReference.Add(new OwnerInit(ownerName));
newActorReference.Add(new LocationInit(cell));
var ios = Actor.TraitInfoOrDefault<IOccupySpaceInfo>();
if (ios != null && ios.SharesCell)
{
var subcell = editorLayer.FreeSubCellAt(cell);
if (subcell != SubCell.Invalid)
newActorReference.Add(new SubCellInit(subcell));
}
var initDict = newActorReference.InitDict;
if (Actor.HasTraitInfo<IFacingInfo>())
initDict.Add(new FacingInit(facing));
if (Actor.HasTraitInfo<TurretedInfo>())
initDict.Add(new TurretFacingInit(facing));
editorLayer.Add(newActorReference);
} }
return true; return true;
@@ -151,4 +128,69 @@ namespace OpenRA.Mods.Common.Widgets
public void Dispose() { } public void Dispose() { }
} }
class AddActorAction : IEditorAction
{
public string Text { get; private set; }
readonly EditorActorLayer editorLayer;
readonly ActorInfo actor;
readonly CPos cell;
readonly PlayerReference owner;
readonly int facing;
EditorActorPreview editorActorPreview;
public AddActorAction(EditorActorLayer editorLayer, ActorInfo actor, CPos cell, PlayerReference owner, int facing)
{
this.editorLayer = editorLayer;
this.actor = actor;
this.cell = cell;
this.owner = owner;
this.facing = facing;
}
public void Execute()
{
Do();
}
public void Do()
{
var ownerName = owner.Name;
var specificOwnerInfo = actor.TraitInfoOrDefault<RequiresSpecificOwnersInfo>();
if (specificOwnerInfo != null && !specificOwnerInfo.ValidOwnerNames.Contains(ownerName))
ownerName = specificOwnerInfo.ValidOwnerNames.First();
var newActorReference = new ActorReference(actor.Name);
newActorReference.Add(new OwnerInit(ownerName));
newActorReference.Add(new LocationInit(cell));
var ios = actor.TraitInfoOrDefault<IOccupySpaceInfo>();
if (ios != null && ios.SharesCell)
{
var subcell = editorLayer.FreeSubCellAt(cell);
if (subcell != SubCell.Invalid)
newActorReference.Add(new SubCellInit(subcell));
}
var initDict = newActorReference.InitDict;
if (actor.HasTraitInfo<IFacingInfo>())
initDict.Add(new FacingInit(facing));
if (actor.HasTraitInfo<TurretedInfo>())
initDict.Add(new TurretFacingInit(facing));
editorActorPreview = editorLayer.Add(newActorReference);
Text = "Added {0} ({1})".F(editorActorPreview.Info.Name, editorActorPreview.ID);
}
public void Undo()
{
editorLayer.Remove(editorActorPreview);
}
}
} }

View File

@@ -36,6 +36,7 @@ namespace OpenRA.Mods.Common.Widgets
readonly EditorSelectionLayer selectionLayer; readonly EditorSelectionLayer selectionLayer;
readonly EditorActorLayer editorLayer; readonly EditorActorLayer editorLayer;
readonly Func<MapCopyFilters> getCopyFilters; readonly Func<MapCopyFilters> getCopyFilters;
readonly EditorActionManager editorActionManager;
State state; State state;
CPos start; CPos start;
@@ -46,6 +47,8 @@ namespace OpenRA.Mods.Common.Widgets
this.editorWidget = editorWidget; this.editorWidget = editorWidget;
worldRenderer = wr; worldRenderer = wr;
editorActionManager = wr.World.WorldActor.Trait<EditorActionManager>();
selectionLayer = wr.World.WorldActor.Trait<EditorSelectionLayer>(); selectionLayer = wr.World.WorldActor.Trait<EditorSelectionLayer>();
editorLayer = wr.World.WorldActor.Trait<EditorActorLayer>(); editorLayer = wr.World.WorldActor.Trait<EditorActorLayer>();
this.getCopyFilters = getCopyFilters; this.getCopyFilters = getCopyFilters;
@@ -143,26 +146,8 @@ namespace OpenRA.Mods.Common.Widgets
} }
} }
foreach (var kv in tiles) var action = new CopyPasteEditorAction(copyFilters, worldRenderer.World.Map, tiles, previews, editorLayer, dest);
{ editorActionManager.Add(action);
if (copyFilters.HasFlag(MapCopyFilters.Terrain))
mapTiles[kv.Key] = kv.Value.Item1;
if (copyFilters.HasFlag(MapCopyFilters.Resources))
mapResources[kv.Key] = kv.Value.Item2;
mapHeight[kv.Key] = kv.Value.Item3;
}
if (copyFilters.HasFlag(MapCopyFilters.Actors))
{
var removeActors = dest.SelectMany(editorLayer.PreviewsAt).Distinct().ToList();
foreach (var preview in removeActors)
editorLayer.Remove(preview);
}
foreach (var kv in previews)
editorLayer.Add(kv.Value);
} }
public void Tick() public void Tick()
@@ -187,4 +172,114 @@ namespace OpenRA.Mods.Common.Widgets
selectionLayer.Clear(); selectionLayer.Clear();
} }
} }
class CopyPasteEditorAction : IEditorAction
{
public string Text { get; private set; }
readonly MapCopyFilters copyFilters;
readonly Dictionary<CPos, Tuple<TerrainTile, ResourceTile, byte>> tiles;
readonly Dictionary<string, ActorReference> previews;
readonly EditorActorLayer editorLayer;
readonly CellRegion dest;
readonly CellLayer<TerrainTile> mapTiles;
readonly CellLayer<byte> mapHeight;
readonly CellLayer<ResourceTile> mapResources;
readonly Queue<UndoCopyPaste> undoCopyPastes = new Queue<UndoCopyPaste>();
readonly Queue<EditorActorPreview> removedActors = new Queue<EditorActorPreview>();
readonly Queue<EditorActorPreview> addedActorPreviews = new Queue<EditorActorPreview>();
public CopyPasteEditorAction(MapCopyFilters copyFilters, Map map,
Dictionary<CPos, Tuple<TerrainTile, ResourceTile, byte>> tiles, Dictionary<string, ActorReference> previews,
EditorActorLayer editorLayer, CellRegion dest)
{
this.copyFilters = copyFilters;
this.tiles = tiles;
this.previews = previews;
this.editorLayer = editorLayer;
this.dest = dest;
mapTiles = map.Tiles;
mapHeight = map.Height;
mapResources = map.Resources;
Text = "Copied {0} tiles".F(tiles.Count);
}
public void Execute()
{
Do();
}
public void Do()
{
foreach (var kv in tiles)
{
undoCopyPastes.Enqueue(new UndoCopyPaste(kv.Key, mapTiles[kv.Key], mapResources[kv.Key], mapHeight[kv.Key]));
if (copyFilters.HasFlag(MapCopyFilters.Terrain))
mapTiles[kv.Key] = kv.Value.Item1;
if (copyFilters.HasFlag(MapCopyFilters.Resources))
mapResources[kv.Key] = kv.Value.Item2;
mapHeight[kv.Key] = kv.Value.Item3;
}
if (copyFilters.HasFlag(MapCopyFilters.Actors))
{
var removeActors = dest.SelectMany(editorLayer.PreviewsAt).Distinct().ToList();
foreach (var preview in removeActors)
{
removedActors.Enqueue(preview);
editorLayer.Remove(preview);
}
}
foreach (var kv in previews)
addedActorPreviews.Enqueue(editorLayer.Add(kv.Value));
}
public void Undo()
{
while (undoCopyPastes.Count > 0)
{
var undoCopyPaste = undoCopyPastes.Dequeue();
var cell = undoCopyPaste.Cell;
if (copyFilters.HasFlag(MapCopyFilters.Terrain))
mapTiles[cell] = undoCopyPaste.MapTile;
if (copyFilters.HasFlag(MapCopyFilters.Resources))
mapResources[cell] = undoCopyPaste.ResourceTile;
mapHeight[cell] = undoCopyPaste.Height;
}
while (addedActorPreviews.Count > 0)
editorLayer.Remove(addedActorPreviews.Dequeue());
if (copyFilters.HasFlag(MapCopyFilters.Actors))
while (removedActors.Count > 0)
editorLayer.Add(removedActors.Dequeue());
}
}
internal class UndoCopyPaste
{
public CPos Cell { get; private set; }
public TerrainTile MapTile { get; private set; }
public ResourceTile ResourceTile { get; private set; }
public byte Height { get; private set; }
public UndoCopyPaste(CPos cell, TerrainTile mapTile, ResourceTile resourceTile, byte height)
{
Cell = cell;
MapTile = mapTile;
ResourceTile = resourceTile;
Height = height;
}
}
} }

View File

@@ -33,6 +33,8 @@ namespace OpenRA.Mods.Common.Widgets
readonly EditorViewportControllerWidget editorWidget; readonly EditorViewportControllerWidget editorWidget;
readonly EditorActorLayer editorLayer; readonly EditorActorLayer editorLayer;
readonly Dictionary<int, ResourceType> resources; readonly Dictionary<int, ResourceType> resources;
readonly EditorActionManager editorActionManager;
public EditorActorPreview SelectedActor; public EditorActorPreview SelectedActor;
int2 worldPixel; int2 worldPixel;
@@ -45,6 +47,8 @@ namespace OpenRA.Mods.Common.Widgets
editorLayer = world.WorldActor.Trait<EditorActorLayer>(); editorLayer = world.WorldActor.Trait<EditorActorLayer>();
resources = world.WorldActor.TraitsImplementing<ResourceType>() resources = world.WorldActor.TraitsImplementing<ResourceType>()
.ToDictionary(r => r.Info.ResourceType, r => r); .ToDictionary(r => r.Info.ResourceType, r => r);
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
} }
long CalculateActorSelectionPriority(EditorActorPreview actor) long CalculateActorSelectionPriority(EditorActorPreview actor)
@@ -74,7 +78,7 @@ namespace OpenRA.Mods.Common.Widgets
var underCursor = editorLayer.PreviewsAt(worldPixel).MinByOrDefault(CalculateActorSelectionPriority); var underCursor = editorLayer.PreviewsAt(worldPixel).MinByOrDefault(CalculateActorSelectionPriority);
var mapResources = world.Map.Resources; var mapResources = world.Map.Resources;
ResourceType type; ResourceType type = null;
if (underCursor != null) if (underCursor != null)
editorWidget.SetTooltip(underCursor.Tooltip); editorWidget.SetTooltip(underCursor.Tooltip);
else if (mapResources.Contains(cell) && resources.TryGetValue(mapResources[cell].Type, out type)) else if (mapResources.Contains(cell) && resources.TryGetValue(mapResources[cell].Type, out type))
@@ -97,10 +101,10 @@ namespace OpenRA.Mods.Common.Widgets
editorWidget.SetTooltip(null); editorWidget.SetTooltip(null);
if (underCursor != null && underCursor != SelectedActor) if (underCursor != null && underCursor != SelectedActor)
editorLayer.Remove(underCursor); editorActionManager.Add(new RemoveActorAction(editorLayer, underCursor));
if (mapResources.Contains(cell) && mapResources[cell].Type != 0) if (mapResources.Contains(cell) && mapResources[cell].Type != 0)
mapResources[cell] = default(ResourceTile); editorActionManager.Add(new RemoveResourceAction(mapResources, cell, type));
} }
return true; return true;
@@ -109,4 +113,69 @@ namespace OpenRA.Mods.Common.Widgets
public void Tick() { } public void Tick() { }
public void Dispose() { } public void Dispose() { }
} }
class RemoveActorAction : IEditorAction
{
public string Text { get; private set; }
readonly EditorActorLayer editorActorLayer;
readonly EditorActorPreview actor;
public RemoveActorAction(EditorActorLayer editorActorLayer, EditorActorPreview actor)
{
this.editorActorLayer = editorActorLayer;
this.actor = actor;
Text = "Removed {0} ({1})".F(actor.Info.Name, actor.ID);
}
public void Execute()
{
Do();
}
public void Do()
{
editorActorLayer.Remove(actor);
}
public void Undo()
{
editorActorLayer.Add(actor);
}
}
class RemoveResourceAction : IEditorAction
{
public string Text { get; private set; }
readonly CellLayer<ResourceTile> mapResources;
readonly CPos cell;
ResourceTile resourceTile;
public RemoveResourceAction(CellLayer<ResourceTile> mapResources, CPos cell, ResourceType type)
{
this.mapResources = mapResources;
this.cell = cell;
Text = "Removed {0}".F(type.Info.TerrainType);
}
public void Execute()
{
Do();
}
public void Do()
{
resourceTile = mapResources[cell];
mapResources[cell] = default(ResourceTile);
}
public void Undo()
{
mapResources[cell] = resourceTile;
}
}
} }

View File

@@ -9,6 +9,7 @@
*/ */
#endregion #endregion
using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
@@ -23,6 +24,10 @@ namespace OpenRA.Mods.Common.Widgets
readonly World world; readonly World world;
readonly EditorViewportControllerWidget editorWidget; readonly EditorViewportControllerWidget editorWidget;
readonly SpriteWidget preview; readonly SpriteWidget preview;
readonly EditorActionManager editorActionManager;
AddResourcesEditorAction action;
bool resourceAdded;
public EditorResourceBrush(EditorViewportControllerWidget editorWidget, ResourceTypeInfo resource, WorldRenderer wr) public EditorResourceBrush(EditorViewportControllerWidget editorWidget, ResourceTypeInfo resource, WorldRenderer wr)
{ {
@@ -30,6 +35,8 @@ namespace OpenRA.Mods.Common.Widgets
ResourceType = resource; ResourceType = resource;
worldRenderer = wr; worldRenderer = wr;
world = wr.World; world = wr.World;
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
action = new AddResourcesEditorAction(world.Map, ResourceType);
preview = editorWidget.Get<SpriteWidget>("DRAG_LAYER_PREVIEW"); preview = editorWidget.Get<SpriteWidget>("DRAG_LAYER_PREVIEW");
preview.Palette = resource.Palette; preview.Palette = resource.Palette;
@@ -65,11 +72,18 @@ namespace OpenRA.Mods.Common.Widgets
var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); var cell = worldRenderer.Viewport.ViewToWorld(mi.Location);
if (mi.Button == MouseButton.Left && AllowResourceAt(cell)) if (mi.Button == MouseButton.Left && mi.Event != MouseInputEvent.Up && AllowResourceAt(cell))
{ {
var type = (byte)ResourceType.ResourceType; var type = (byte)ResourceType.ResourceType;
var index = (byte)ResourceType.MaxDensity; var index = (byte)ResourceType.MaxDensity;
world.Map.Resources[cell] = new ResourceTile(type, index); action.Add(new CellResource(cell, world.Map.Resources[cell], new ResourceTile(type, index)));
resourceAdded = true;
}
else if (resourceAdded && mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Up)
{
editorActionManager.Add(action);
action = new AddResourcesEditorAction(world.Map, ResourceType);
resourceAdded = false;
} }
return true; return true;
@@ -112,4 +126,63 @@ namespace OpenRA.Mods.Common.Widgets
public void Dispose() { } public void Dispose() { }
} }
struct CellResource
{
public readonly CPos Cell;
public readonly ResourceTile ResourceTile;
public readonly ResourceTile NewResourceTile;
public CellResource(CPos cell, ResourceTile resourceTile, ResourceTile newResourceTile)
{
Cell = cell;
ResourceTile = resourceTile;
NewResourceTile = newResourceTile;
}
}
class AddResourcesEditorAction : IEditorAction
{
public string Text { get; private set; }
readonly Map map;
readonly ResourceTypeInfo resourceType;
readonly List<CellResource> cellResources = new List<CellResource>();
public AddResourcesEditorAction(Map map, ResourceTypeInfo resourceType)
{
this.map = map;
this.resourceType = resourceType;
}
public void Execute()
{
}
public void Do()
{
foreach (var resourceCell in cellResources)
SetTile(resourceCell.Cell, resourceCell.NewResourceTile);
}
void SetTile(CPos cell, ResourceTile tile)
{
map.Resources[cell] = tile;
}
public void Undo()
{
foreach (var resourceCell in cellResources)
SetTile(resourceCell.Cell, resourceCell.ResourceTile);
}
public void Add(CellResource cellResource)
{
SetTile(cellResource.Cell, cellResource.NewResourceTile);
cellResources.Add(cellResource);
var cellText = cellResources.Count != 1 ? "cells" : "cell";
Text = "Added {0} {1} of {2}".F(cellResources.Count, cellText, resourceType.TerrainType);
}
}
} }

View File

@@ -13,6 +13,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives; using OpenRA.Primitives;
namespace OpenRA.Mods.Common.Widgets namespace OpenRA.Mods.Common.Widgets
@@ -26,6 +27,7 @@ namespace OpenRA.Mods.Common.Widgets
readonly EditorViewportControllerWidget editorWidget; readonly EditorViewportControllerWidget editorWidget;
readonly TerrainTemplatePreviewWidget preview; readonly TerrainTemplatePreviewWidget preview;
readonly Rectangle bounds; readonly Rectangle bounds;
readonly EditorActionManager editorActionManager;
bool painting; bool painting;
@@ -36,6 +38,8 @@ namespace OpenRA.Mods.Common.Widgets
worldRenderer = wr; worldRenderer = wr;
world = wr.World; world = wr.World;
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
preview = editorWidget.Get<TerrainTemplatePreviewWidget>("DRAG_TILE_PREVIEW"); preview = editorWidget.Get<TerrainTemplatePreviewWidget>("DRAG_TILE_PREVIEW");
preview.GetScale = () => worldRenderer.Viewport.Zoom; preview.GetScale = () => worldRenderer.Viewport.Zoom;
preview.IsVisible = () => editorWidget.CurrentBrush == this; preview.IsVisible = () => editorWidget.CurrentBrush == this;
@@ -97,33 +101,13 @@ namespace OpenRA.Mods.Common.Widgets
void PaintCell(CPos cell, bool isMoving) void PaintCell(CPos cell, bool isMoving)
{ {
var map = world.Map; var map = world.Map;
var mapTiles = map.Tiles;
var mapHeight = map.Height;
var tileset = map.Rules.TileSet; var tileset = map.Rules.TileSet;
var template = tileset.Templates[Template]; var template = tileset.Templates[Template];
var baseHeight = mapHeight.Contains(cell) ? mapHeight[cell] : (byte)0;
if (isMoving && PlacementOverlapsSameTemplate(template, cell)) if (isMoving && PlacementOverlapsSameTemplate(template, cell))
return; return;
var i = 0; editorActionManager.Add(new PaintTileEditorAction(Template, map, cell));
for (var y = 0; y < template.Size.Y; y++)
{
for (var x = 0; x < template.Size.X; x++, i++)
{
if (template.Contains(i) && template[i] != null)
{
var index = template.PickAny ? (byte)Game.CosmeticRandom.Next(0, template.TilesCount) : (byte)i;
var c = cell + new CVec(x, y);
if (!mapTiles.Contains(c))
continue;
mapTiles[c] = new TerrainTile(Template, index);
mapHeight[c] = (byte)(baseHeight + template[index].Height).Clamp(0, map.Grid.MaximumTerrainHeight);
}
}
}
} }
void FloodFillWithBrush(CPos cell, bool isMoving) void FloodFillWithBrush(CPos cell, bool isMoving)
@@ -239,4 +223,87 @@ namespace OpenRA.Mods.Common.Widgets
public void Dispose() { } public void Dispose() { }
} }
class PaintTileEditorAction : IEditorAction
{
public string Text { get; private set; }
readonly ushort template;
readonly Map map;
readonly CPos cell;
readonly Queue<UndoTile> undoTiles = new Queue<UndoTile>();
readonly TerrainTemplateInfo terrainTemplate;
public PaintTileEditorAction(ushort template, Map map, CPos cell)
{
this.template = template;
this.map = map;
this.cell = cell;
var tileset = map.Rules.TileSet;
terrainTemplate = tileset.Templates[template];
Text = "Added tile {0}".F(terrainTemplate.Id);
}
public void Execute()
{
Do();
}
public void Do()
{
var mapTiles = map.Tiles;
var mapHeight = map.Height;
var baseHeight = mapHeight.Contains(cell) ? mapHeight[cell] : (byte)0;
var i = 0;
for (var y = 0; y < terrainTemplate.Size.Y; y++)
{
for (var x = 0; x < terrainTemplate.Size.X; x++, i++)
{
if (terrainTemplate.Contains(i) && terrainTemplate[i] != null)
{
var index = terrainTemplate.PickAny ? (byte)Game.CosmeticRandom.Next(0, terrainTemplate.TilesCount) : (byte)i;
var c = cell + new CVec(x, y);
if (!mapTiles.Contains(c))
continue;
undoTiles.Enqueue(new UndoTile(c, mapTiles[c], mapHeight[c]));
mapTiles[c] = new TerrainTile(template, index);
mapHeight[c] = (byte)(baseHeight + terrainTemplate[index].Height).Clamp(0, map.Grid.MaximumTerrainHeight);
}
}
}
}
public void Undo()
{
var mapTiles = map.Tiles;
var mapHeight = map.Height;
while (undoTiles.Count > 0)
{
var undoTile = undoTiles.Dequeue();
mapTiles[undoTile.Cell] = undoTile.MapTile;
mapHeight[undoTile.Cell] = undoTile.Height;
}
}
}
class UndoTile
{
public CPos Cell { get; private set; }
public TerrainTile MapTile { get; private set; }
public byte Height { get; private set; }
public UndoTile(CPos cell, TerrainTile mapTile, byte height)
{
Cell = cell;
MapTile = mapTile;
Height = height;
}
}
} }

View File

@@ -0,0 +1,172 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 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, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class EditorActionManagerInfo : TraitInfo<EditorActionManager> { }
public class EditorActionManager : IWorldLoaded
{
readonly Stack<EditorActionContainer> undoStack = new Stack<EditorActionContainer>();
readonly Stack<EditorActionContainer> redoStack = new Stack<EditorActionContainer>();
public event Action<EditorActionContainer> ItemAdded;
public event Action<EditorActionContainer> ItemRemoved;
public event Action OnChange;
int nextId;
public void WorldLoaded(World w, WorldRenderer wr)
{
Add(new OpenMapAction());
}
public void Add(IEditorAction editorAction)
{
editorAction.Execute();
if (undoStack.Count > 0)
undoStack.Peek().Status = EditorActionStatus.History;
var actionContainer = new EditorActionContainer(nextId++, editorAction);
ClearRedo();
undoStack.Push(actionContainer);
if (ItemAdded != null)
ItemAdded(actionContainer);
}
public void Undo()
{
if (!HasUndos())
return;
var editorAction = undoStack.Pop();
undoStack.Peek().Status = EditorActionStatus.Active;
editorAction.Action.Undo();
editorAction.Status = EditorActionStatus.Future;
redoStack.Push(editorAction);
if (OnChange != null)
OnChange();
}
void ClearRedo()
{
while (HasRedos())
{
var item = redoStack.Pop();
if (ItemRemoved != null)
ItemRemoved(item);
}
}
public void Redo()
{
if (!HasRedos())
return;
var editorAction = redoStack.Pop();
editorAction.Status = EditorActionStatus.Active;
editorAction.Action.Do();
undoStack.Peek().Status = EditorActionStatus.History;
undoStack.Push(editorAction);
if (OnChange != null)
OnChange();
}
public bool HasUndos()
{
// Preserve the initial OpenMapAction.
return undoStack.Count > 1;
}
public bool HasRedos()
{
return redoStack.Count > 0;
}
public void Rewind(int id)
{
while (undoStack.Peek().Id != id)
Undo();
}
public void Forward(int id)
{
while (undoStack.Peek().Id != id)
Redo();
}
}
public enum EditorActionStatus
{
History,
Active,
Future,
}
public interface IEditorAction
{
void Execute();
void Do();
void Undo();
string Text { get; }
}
class OpenMapAction : IEditorAction
{
public OpenMapAction()
{
Text = "Opened";
}
public void Execute()
{
}
public void Do()
{
}
public void Undo()
{
}
public string Text { get; private set; }
public EditorActionStatus Status { get; set; }
}
public class EditorActionContainer
{
public int Id { get; private set; }
public IEditorAction Action { get; private set; }
public EditorActionStatus Status { get; set; }
public EditorActionContainer(int id, IEditorAction action)
{
Id = id;
Action = action;
Status = EditorActionStatus.Active;
}
}
}

View File

@@ -110,8 +110,15 @@ namespace OpenRA.Mods.Common.Traits
var owner = Players.Players[reference.InitDict.Get<OwnerInit>().PlayerName]; var owner = Players.Players[reference.InitDict.Get<OwnerInit>().PlayerName];
var preview = new EditorActorPreview(worldRenderer, id, reference, owner); var preview = new EditorActorPreview(worldRenderer, id, reference, owner);
previews.Add(preview);
Add(preview, initialSetup);
return preview;
}
public void Add(EditorActorPreview preview, bool initialSetup = false)
{
previews.Add(preview);
if (!preview.Bounds.IsEmpty) if (!preview.Bounds.IsEmpty)
screenMap.Add(preview, preview.Bounds); screenMap.Add(preview, preview.Bounds);
@@ -126,11 +133,9 @@ namespace OpenRA.Mods.Common.Traits
{ {
UpdateNeighbours(preview.Footprint); UpdateNeighbours(preview.Footprint);
if (reference.Type == "mpspawn") if (preview.Actor.Type == "mpspawn")
SyncMultiplayerCount(); SyncMultiplayerCount();
} }
return preview;
} }
public void Remove(EditorActorPreview preview) public void Remove(EditorActorPreview preview)
@@ -262,7 +267,7 @@ namespace OpenRA.Mods.Common.Traits
string NextActorName() string NextActorName()
{ {
var id = previews.Count(); var id = previews.Count;
var possibleName = "Actor" + id.ToString(); var possibleName = "Actor" + id.ToString();
while (previews.Any(p => p.ID == possibleName)) while (previews.Any(p => p.ID == possibleName))

View File

@@ -20,7 +20,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
public class EditorActorPreview public class EditorActorPreview : IEquatable<EditorActorPreview>
{ {
public readonly string DescriptiveName; public readonly string DescriptiveName;
public readonly ActorInfo Info; public readonly ActorInfo Info;
@@ -28,6 +28,7 @@ namespace OpenRA.Mods.Common.Traits
public readonly IReadOnlyDictionary<CPos, SubCell> Footprint; public readonly IReadOnlyDictionary<CPos, SubCell> Footprint;
public readonly Rectangle Bounds; public readonly Rectangle Bounds;
public readonly SelectionBoxRenderable SelectionBox; public readonly SelectionBoxRenderable SelectionBox;
public readonly ActorReference Actor;
public string Tooltip public string Tooltip
{ {
@@ -43,7 +44,6 @@ namespace OpenRA.Mods.Common.Traits
public SubCell SubCell { get; private set; } public SubCell SubCell { get; private set; }
public bool Selected { get; set; } public bool Selected { get; set; }
readonly ActorReference actor;
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
readonly TooltipInfoBase tooltip; readonly TooltipInfoBase tooltip;
IActorPreview[] previews; IActorPreview[] previews;
@@ -51,7 +51,7 @@ namespace OpenRA.Mods.Common.Traits
public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference actor, PlayerReference owner) public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference actor, PlayerReference owner)
{ {
ID = id; ID = id;
this.actor = actor; Actor = actor;
Owner = owner; Owner = owner;
this.worldRenderer = worldRenderer; this.worldRenderer = worldRenderer;
@@ -120,25 +120,25 @@ namespace OpenRA.Mods.Common.Traits
public void ReplaceInit<T>(T init) public void ReplaceInit<T>(T init)
{ {
var original = actor.InitDict.GetOrDefault<T>(); var original = Actor.InitDict.GetOrDefault<T>();
if (original != null) if (original != null)
actor.InitDict.Remove(original); Actor.InitDict.Remove(original);
actor.InitDict.Add(init); Actor.InitDict.Add(init);
GeneratePreviews(); GeneratePreviews();
} }
public void RemoveInit<T>() public void RemoveInit<T>()
{ {
var original = actor.InitDict.GetOrDefault<T>(); var original = Actor.InitDict.GetOrDefault<T>();
if (original != null) if (original != null)
actor.InitDict.Remove(original); Actor.InitDict.Remove(original);
GeneratePreviews(); GeneratePreviews();
} }
public T Init<T>() public T Init<T>()
{ {
return actor.InitDict.GetOrDefault<T>(); return Actor.InitDict.GetOrDefault<T>();
} }
public MiniYaml Save() public MiniYaml Save()
@@ -154,7 +154,7 @@ namespace OpenRA.Mods.Common.Traits
return true; return true;
}; };
return actor.Save(saveInit); return Actor.Save(saveInit);
} }
WPos PreviewPosition(World world, TypeDictionary init) WPos PreviewPosition(World world, TypeDictionary init)
@@ -167,7 +167,7 @@ namespace OpenRA.Mods.Common.Traits
var cell = init.Get<LocationInit>().Value(world); var cell = init.Get<LocationInit>().Value(world);
var offset = WVec.Zero; var offset = WVec.Zero;
var subCellInit = actor.InitDict.GetOrDefault<SubCellInit>(); var subCellInit = Actor.InitDict.GetOrDefault<SubCellInit>();
var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any; var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any;
var buildingInfo = Info.TraitInfoOrDefault<BuildingInfo>(); var buildingInfo = Info.TraitInfoOrDefault<BuildingInfo>();
@@ -182,7 +182,7 @@ namespace OpenRA.Mods.Common.Traits
void GeneratePreviews() void GeneratePreviews()
{ {
var init = new ActorPreviewInitializer(Info, worldRenderer, actor.InitDict); var init = new ActorPreviewInitializer(Info, worldRenderer, Actor.InitDict);
previews = Info.TraitInfos<IRenderActorPreviewInfo>() previews = Info.TraitInfos<IRenderActorPreviewInfo>()
.SelectMany(rpi => rpi.RenderPreview(init)) .SelectMany(rpi => rpi.RenderPreview(init))
.ToArray(); .ToArray();
@@ -190,12 +190,39 @@ namespace OpenRA.Mods.Common.Traits
public ActorReference Export() public ActorReference Export()
{ {
return new ActorReference(actor.Type, actor.Save().ToDictionary()); return new ActorReference(Actor.Type, Actor.Save().ToDictionary());
} }
public override string ToString() public override string ToString()
{ {
return "{0} {1}".F(Info.Name, ID); return "{0} {1}".F(Info.Name, ID);
} }
public bool Equals(EditorActorPreview other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return string.Equals(ID, other.ID, StringComparison.OrdinalIgnoreCase);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != GetType())
return false;
return Equals((EditorActorPreview)obj);
}
public override int GetHashCode()
{
return ID != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(ID) : 0;
}
} }
} }

View File

@@ -11,6 +11,7 @@
using System; using System;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Widgets; using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets namespace OpenRA.Mods.Common.Widgets
@@ -25,6 +26,7 @@ namespace OpenRA.Mods.Common.Widgets
readonly Lazy<TooltipContainerWidget> tooltipContainer; readonly Lazy<TooltipContainerWidget> tooltipContainer;
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
readonly EditorActionManager editorActionManager;
bool enableTooltips; bool enableTooltips;
@@ -34,6 +36,14 @@ namespace OpenRA.Mods.Common.Widgets
this.worldRenderer = worldRenderer; this.worldRenderer = worldRenderer;
tooltipContainer = Exts.Lazy(() => Ui.Root.Get<TooltipContainerWidget>(TooltipContainer)); tooltipContainer = Exts.Lazy(() => Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
CurrentBrush = DefaultBrush = new EditorDefaultBrush(this, worldRenderer); CurrentBrush = DefaultBrush = new EditorDefaultBrush(this, worldRenderer);
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
editorActionManager.OnChange += EditorActionManagerOnChange;
}
void EditorActionManagerOnChange()
{
DefaultBrush.SelectedActor = null;
} }
public void ClearBrush() { SetBrush(null); } public void ClearBrush() { SetBrush(null); }
@@ -109,5 +119,11 @@ namespace OpenRA.Mods.Common.Widgets
cachedViewportPosition = worldRenderer.Viewport.CenterPosition; cachedViewportPosition = worldRenderer.Viewport.CenterPosition;
CurrentBrush.Tick(); CurrentBrush.Tick();
} }
public override void Removed()
{
base.Removed();
editorActionManager.OnChange -= EditorActionManagerOnChange;
}
} }
} }

View File

@@ -26,6 +26,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
readonly EditorActorLayer editorActorLayer; readonly EditorActorLayer editorActorLayer;
readonly EditorActionManager editorActionManager;
readonly EditorViewportControllerWidget editor; readonly EditorViewportControllerWidget editor;
readonly BackgroundWidget actorEditPanel; readonly BackgroundWidget actorEditPanel;
readonly LabelWidget typeLabel; readonly LabelWidget typeLabel;
@@ -48,6 +49,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
string initialActorID; string initialActorID;
EditorActorPreview currentActorInner; EditorActorPreview currentActorInner;
EditActorPreview editActorPreview;
EditorActorPreview CurrentActor EditorActorPreview CurrentActor
{ {
get get
@@ -61,7 +64,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
return; return;
if (currentActorInner != null) if (currentActorInner != null)
{
Reset();
currentActorInner.Selected = false; currentActorInner.Selected = false;
}
currentActorInner = value; currentActorInner = value;
if (currentActorInner != null) if (currentActorInner != null)
@@ -74,6 +80,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
this.worldRenderer = worldRenderer; this.worldRenderer = worldRenderer;
editorActorLayer = world.WorldActor.Trait<EditorActorLayer>(); editorActorLayer = world.WorldActor.Trait<EditorActorLayer>();
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
editor = widget.Parent.Get<EditorViewportControllerWidget>("MAP_EDITOR"); editor = widget.Parent.Get<EditorViewportControllerWidget>("MAP_EDITOR");
actorEditPanel = editor.Get<BackgroundWidget>("ACTOR_EDIT_PANEL"); actorEditPanel = editor.Get<BackgroundWidget>("ACTOR_EDIT_PANEL");
@@ -88,7 +96,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
initContainer.RemoveChildren(); initContainer.RemoveChildren();
var deleteButton = actorEditPanel.Get<ButtonWidget>("DELETE_BUTTON"); var deleteButton = actorEditPanel.Get<ButtonWidget>("DELETE_BUTTON");
var closeButton = actorEditPanel.Get<ButtonWidget>("CLOSE_BUTTON"); var cancelButton = actorEditPanel.Get<ButtonWidget>("CANCEL_BUTTON");
var okButton = actorEditPanel.Get<ButtonWidget>("OK_BUTTON");
actorIDErrorLabel = actorEditPanel.Get<LabelWidget>("ACTOR_ID_ERROR_LABEL"); actorIDErrorLabel = actorEditPanel.Get<LabelWidget>("ACTOR_ID_ERROR_LABEL");
actorIDErrorLabel.IsVisible = () => actorIDStatus != ActorIDStatus.Normal; actorIDErrorLabel.IsVisible = () => actorIDStatus != ActorIDStatus.Normal;
@@ -99,7 +108,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (logicArgs.TryGetValue("EditPanelPadding", out yaml)) if (logicArgs.TryGetValue("EditPanelPadding", out yaml))
editPanelPadding = FieldLoader.GetValue<int>("EditPanelPadding", yaml.Value); editPanelPadding = FieldLoader.GetValue<int>("EditPanelPadding", yaml.Value);
closeButton.OnClick = Close; okButton.IsDisabled = () => !IsValid() || !editActorPreview.IsDirty;
okButton.OnClick = Save;
cancelButton.OnClick = Cancel;
deleteButton.OnClick = Delete; deleteButton.OnClick = Delete;
actorEditPanel.IsVisible = () => CurrentActor != null actorEditPanel.IsVisible = () => CurrentActor != null
&& editor.CurrentBrush == editor.DefaultBrush && editor.CurrentBrush == editor.DefaultBrush
@@ -113,15 +124,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
actorIDField.OnTextEdited = () => actorIDField.OnTextEdited = () =>
{ {
if (string.IsNullOrWhiteSpace(actorIDField.Text)) var actorId = actorIDField.Text.Trim();
if (string.IsNullOrWhiteSpace(actorId))
{ {
nextActorIDStatus = ActorIDStatus.Empty; nextActorIDStatus = ActorIDStatus.Empty;
return; return;
} }
// Check for duplicate actor ID // Check for duplicate actor ID
var actorId = actorIDField.Text.ToLowerInvariant(); if (CurrentActor.ID.Equals(actorId, StringComparison.OrdinalIgnoreCase))
if (CurrentActor.ID.ToLowerInvariant() != actorId)
{ {
if (editorActorLayer[actorId] != null) if (editorActorLayer[actorId] != null)
{ {
@@ -130,23 +141,30 @@ namespace OpenRA.Mods.Common.Widgets.Logic
} }
} }
SetActorID(world, actorId); SetActorID(actorId);
nextActorIDStatus = ActorIDStatus.Normal;
}; };
actorIDField.OnLoseFocus = () => actorIDField.OnLoseFocus = () =>
{ {
// Reset invalid IDs back to their starting value // Reset invalid IDs back to their starting value
if (actorIDStatus != ActorIDStatus.Normal) if (actorIDStatus != ActorIDStatus.Normal)
SetActorID(world, initialActorID); SetActorID(initialActorID);
}; };
} }
void SetActorID(World world, string actorId) void SetActorID(string actorId)
{ {
CurrentActor.ID = actorId; editActorPreview.SetActorID(actorId);
nextActorIDStatus = ActorIDStatus.Normal; nextActorIDStatus = ActorIDStatus.Normal;
} }
bool IsValid()
{
return nextActorIDStatus == ActorIDStatus.Normal;
}
public override void Tick() public override void Tick()
{ {
if (actorIDStatus != nextActorIDStatus) if (actorIDStatus != nextActorIDStatus)
@@ -183,6 +201,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
lastScrollTime = 0; // Ensure visible lastScrollTime = 0; // Ensure visible
CurrentActor = actor; CurrentActor = actor;
editActorPreview = new EditActorPreview(CurrentActor);
initialActorID = actorIDField.Text = actor.ID; initialActorID = actorIDField.Text = actor.ID;
var font = Game.Renderer.Fonts[typeLabel.Font]; var font = Game.Renderer.Fonts[typeLabel.Font];
@@ -203,13 +223,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var ownerDropdown = ownerContainer.Get<DropDownButtonWidget>("OPTION"); var ownerDropdown = ownerContainer.Get<DropDownButtonWidget>("OPTION");
var selectedOwner = actor.Owner; var selectedOwner = actor.Owner;
Action<EditorActorPreview, PlayerReference> updateOwner = (preview, reference) =>
{
preview.Owner = reference;
preview.ReplaceInit(new OwnerInit(reference.Name));
};
var ownerHandler = new EditorActorOptionActionHandle<PlayerReference>(updateOwner, actor.Owner);
editActorPreview.Add(ownerHandler);
Func<PlayerReference, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) => Func<PlayerReference, ScrollItemWidget, ScrollItemWidget> setupItem = (option, template) =>
{ {
var item = ScrollItemWidget.Setup(template, () => selectedOwner == option, () => var item = ScrollItemWidget.Setup(template, () => selectedOwner == option, () =>
{ {
selectedOwner = option; selectedOwner = option;
CurrentActor.Owner = selectedOwner; updateOwner(CurrentActor, selectedOwner);
CurrentActor.ReplaceInit(new OwnerInit(selectedOwner.Name)); ownerHandler.OnChange(option);
}); });
item.Get<LabelWidget>("LABEL").GetText = () => option.Name; item.Get<LabelWidget>("LABEL").GetText = () => option.Name;
@@ -248,8 +277,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
slider.MaximumValue = so.MaxValue; slider.MaximumValue = so.MaxValue;
slider.Ticks = so.Ticks; slider.Ticks = so.Ticks;
var editorActionHandle = new EditorActorOptionActionHandle<float>(so.OnChange, so.GetValue(actor));
editActorPreview.Add(editorActionHandle);
slider.GetValue = () => so.GetValue(actor); slider.GetValue = () => so.GetValue(actor);
slider.OnChange += value => so.OnChange(actor, value); slider.OnChange += value => so.OnChange(actor, value);
slider.OnChange += value => editorActionHandle.OnChange(value);
initContainer.AddChild(sliderContainer); initContainer.AddChild(sliderContainer);
} }
@@ -261,12 +294,19 @@ namespace OpenRA.Mods.Common.Widgets.Logic
initContainer.Bounds.Height += dropdownContainer.Bounds.Height; initContainer.Bounds.Height += dropdownContainer.Bounds.Height;
dropdownContainer.Get<LabelWidget>("LABEL").GetText = () => ddo.Name; dropdownContainer.Get<LabelWidget>("LABEL").GetText = () => ddo.Name;
var editorActionHandle = new EditorActorOptionActionHandle<string>(ddo.OnChange, ddo.GetValue(actor));
editActorPreview.Add(editorActionHandle);
var dropdown = dropdownContainer.Get<DropDownButtonWidget>("OPTION"); var dropdown = dropdownContainer.Get<DropDownButtonWidget>("OPTION");
Func<KeyValuePair<string, string>, ScrollItemWidget, ScrollItemWidget> dropdownSetup = (option, template) => Func<KeyValuePair<string, string>, ScrollItemWidget, ScrollItemWidget> dropdownSetup = (option, template) =>
{ {
var item = ScrollItemWidget.Setup(template, var item = ScrollItemWidget.Setup(template,
() => ddo.GetValue(actor) == option.Key, () => ddo.GetValue(actor) == option.Key,
() => ddo.OnChange(actor, option.Key)); () =>
{
ddo.OnChange(actor, option.Key);
editorActionHandle.OnChange(option.Key);
});
item.Get<LabelWidget>("LABEL").GetText = () => option.Value; item.Get<LabelWidget>("LABEL").GetText = () => option.Value;
return item; return item;
@@ -298,16 +338,180 @@ namespace OpenRA.Mods.Common.Widgets.Logic
void Delete() void Delete()
{ {
if (CurrentActor != null) if (CurrentActor != null)
editorActorLayer.Remove(CurrentActor); editorActionManager.Add(new RemoveActorAction(editorActorLayer, CurrentActor));
Close(); Close();
} }
void Cancel()
{
Reset();
Close();
}
void Reset()
{
if (editActorPreview != null)
editActorPreview.Reset();
}
void Close() void Close()
{ {
actorIDField.YieldKeyboardFocus(); actorIDField.YieldKeyboardFocus();
editor.DefaultBrush.SelectedActor = null; editor.DefaultBrush.SelectedActor = null;
CurrentActor = null; CurrentActor = null;
} }
void Save()
{
editorActionManager.Add(new EditActorEditorAction(editorActorLayer, CurrentActor, editActorPreview.GetDirtyHandles()));
editActorPreview = null;
Close();
}
}
public class EditorActorOptionActionHandle<T> : IEditActorHandle
{
readonly Action<EditorActorPreview, T> change;
T value;
readonly T initialValue;
public EditorActorOptionActionHandle(Action<EditorActorPreview, T> change, T value)
{
this.change = change;
this.value = value;
initialValue = value;
}
public void OnChange(T value)
{
IsDirty = !EqualityComparer<T>.Default.Equals(initialValue, value);
this.value = value;
}
public void Do(EditorActorPreview actor)
{
change(actor, value);
}
public void Undo(EditorActorPreview actor)
{
change(actor, initialValue);
}
public bool IsDirty { get; private set; }
}
public interface IEditActorHandle
{
void Do(EditorActorPreview actor);
void Undo(EditorActorPreview actor);
bool IsDirty { get; }
}
class EditActorEditorAction : IEditorAction
{
public string Text { get; private set; }
readonly IEnumerable<IEditActorHandle> handles;
readonly EditorActorLayer editorActorLayer;
EditorActorPreview actor;
readonly string actorId;
public EditActorEditorAction(EditorActorLayer editorActorLayer, EditorActorPreview actor, IEnumerable<IEditActorHandle> handles)
{
this.editorActorLayer = editorActorLayer;
actorId = actor.ID;
this.actor = actor;
this.handles = handles;
Text = "Edited {0} ({1})".F(actor.Info.Name, actor.ID);
}
public void Execute()
{
}
public void Do()
{
actor = editorActorLayer[actorId.ToLowerInvariant()];
foreach (var editorActionHandle in handles)
editorActionHandle.Do(actor);
}
public void Undo()
{
foreach (var editorActionHandle in handles)
editorActionHandle.Undo(actor);
}
}
class EditActorPreview
{
readonly EditorActorPreview actor;
readonly SetActorIdAction setActorIdAction;
readonly List<IEditActorHandle> handles = new List<IEditActorHandle>();
public EditActorPreview(EditorActorPreview actor)
{
this.actor = actor;
setActorIdAction = new SetActorIdAction(actor.ID);
handles.Add(setActorIdAction);
}
public bool IsDirty
{
get { return handles.Any(h => h.IsDirty); }
}
public void SetActorID(string actorID)
{
setActorIdAction.Set(actorID);
}
public void Add(IEditActorHandle editActor)
{
handles.Add(editActor);
}
public IEnumerable<IEditActorHandle> GetDirtyHandles()
{
return handles.Where(h => h.IsDirty);
}
public void Reset()
{
foreach (var handle in handles.Where(h => h.IsDirty))
handle.Undo(actor);
}
}
public class SetActorIdAction : IEditActorHandle
{
readonly string initial;
string newID;
public void Set(string actorId)
{
IsDirty = initial != actorId;
newID = actorId;
}
public SetActorIdAction(string initial)
{
this.initial = initial;
}
public void Do(EditorActorPreview actor)
{
actor.ID = newID;
}
public void Undo(EditorActorPreview actor)
{
actor.ID = initial;
}
public bool IsDirty { get; private set; }
} }
} }

View File

@@ -0,0 +1,71 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 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, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class HistoryLogLogic : ChromeLogic
{
readonly ScrollPanelWidget panel;
readonly EditorActionManager editorActionManager;
readonly ScrollItemWidget template;
readonly Dictionary<EditorActionContainer, ScrollItemWidget> states = new Dictionary<EditorActionContainer, ScrollItemWidget>();
[ObjectCreator.UseCtor]
public HistoryLogLogic(Widget widget, World world, WorldRenderer worldRenderer, Dictionary<string, MiniYaml> logicArgs)
{
panel = widget.Get<ScrollPanelWidget>("HISTORY_LIST");
template = panel.Get<ScrollItemWidget>("HISTORY_TEMPLATE");
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
editorActionManager.ItemAdded += ItemAdded;
editorActionManager.ItemRemoved += ItemRemoved;
}
void ItemAdded(EditorActionContainer editorAction)
{
var item = ScrollItemWidget.Setup(template, () => false, () =>
{
if (editorAction.Status == EditorActionStatus.History)
editorActionManager.Rewind(editorAction.Id);
else if (editorAction.Status == EditorActionStatus.Future)
editorActionManager.Forward(editorAction.Id);
});
var titleLabel = item.Get<LabelWidget>("TITLE");
var textColor = template.TextColor;
var futureTextColor = template.TextColorDisabled;
titleLabel.GetText = () => editorAction.Action.Text;
titleLabel.GetColor = () => editorAction.Status == EditorActionStatus.Future ? futureTextColor : textColor;
item.IsSelected = () => editorAction.Status == EditorActionStatus.Active;
panel.AddChild(item);
states[editorAction] = item;
}
void ItemRemoved(EditorActionContainer editorAction)
{
var widget = states[editorAction];
panel.RemoveChild(widget);
states.Remove(editorAction);
}
}
}

View File

@@ -128,6 +128,21 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (reslayer != null) if (reslayer != null)
cashLabel.GetText = () => "$ {0}".F(reslayer.NetWorth); cashLabel.GetText = () => "$ {0}".F(reslayer.NetWorth);
} }
var actionManager = world.WorldActor.Trait<EditorActionManager>();
var undoButton = widget.GetOrNull<ButtonWidget>("UNDO_BUTTON");
if (undoButton != null)
{
undoButton.IsDisabled = () => !actionManager.HasUndos();
undoButton.OnClick = () => actionManager.Undo();
}
var redoButton = widget.GetOrNull<ButtonWidget>("REDO_BUTTON");
if (redoButton != null)
{
redoButton.IsDisabled = () => !actionManager.HasRedos();
redoButton.OnClick = () => actionManager.Redo();
}
} }
Widget CreateCategoriesPanel() Widget CreateCategoriesPanel()

View File

@@ -18,7 +18,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
readonly Widget widget; readonly Widget widget;
protected enum MenuType { Tiles, Layers, Actors } protected enum MenuType { Tiles, Layers, Actors, History }
protected MenuType menuType = MenuType.Tiles; protected MenuType menuType = MenuType.Tiles;
readonly Widget tabContainer; readonly Widget tabContainer;
@@ -31,6 +31,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
SetupTab("TILES_TAB", "TILE_WIDGETS", MenuType.Tiles); SetupTab("TILES_TAB", "TILE_WIDGETS", MenuType.Tiles);
SetupTab("OVERLAYS_TAB", "LAYER_WIDGETS", MenuType.Layers); SetupTab("OVERLAYS_TAB", "LAYER_WIDGETS", MenuType.Layers);
SetupTab("ACTORS_TAB", "ACTOR_WIDGETS", MenuType.Actors); SetupTab("ACTORS_TAB", "ACTOR_WIDGETS", MenuType.Actors);
SetupTab("HISTORY_TAB", "HISTORY_WIDGETS", MenuType.History);
} }
void SetupTab(string buttonId, string tabId, MenuType tabType) void SetupTab(string buttonId, string tabId, MenuType tabType)

View File

@@ -292,15 +292,21 @@ Container@EDITOR_WORLD_ROOT:
Children: Children:
Button@DELETE_BUTTON: Button@DELETE_BUTTON:
X: 4 X: 4
Width: 85 Width: 75
Height: 25 Height: 25
Text: Delete Text: Delete
Font: Bold Font: Bold
Button@CLOSE_BUTTON: Button@CANCEL_BUTTON:
X: 180 X: 110
Width: 85 Width: 75
Height: 25 Height: 25
Text: Close Text: Cancel
Font: Bold
Button@OK_BUTTON:
X: 190
Width: 75
Height: 25
Text: OK
Font: Bold Font: Bold
ViewportController: ViewportController:
Width: WINDOW_RIGHT Width: WINDOW_RIGHT
@@ -318,10 +324,10 @@ Container@EDITOR_WORLD_ROOT:
BookmarkRestoreKeyPrefix: MapBookmarkRestore BookmarkRestoreKeyPrefix: MapBookmarkRestore
BookmarkKeyCount: 4 BookmarkKeyCount: 4
Background@RADAR_BG: Background@RADAR_BG:
X: WINDOW_RIGHT - 255 X: WINDOW_RIGHT - 295
Y: 5 Y: 5
Width: 250 Width: 290
Height: 250 Height: 290
Background: panel-gray Background: panel-gray
Children: Children:
Radar@INGAME_RADAR: Radar@INGAME_RADAR:
@@ -332,7 +338,7 @@ Container@EDITOR_WORLD_ROOT:
MenuButton@OPTIONS_BUTTON: MenuButton@OPTIONS_BUTTON:
Logic: MenuButtonsChromeLogic Logic: MenuButtonsChromeLogic
Key: escape Key: escape
X: WINDOW_RIGHT - 254 - WIDTH X: WINDOW_RIGHT - 294 - WIDTH
Y: 5 Y: 5
Width: 30 Width: 30
Height: 25 Height: 25
@@ -346,10 +352,10 @@ Container@EDITOR_WORLD_ROOT:
ImageName: options ImageName: options
Container@TILE_WIDGETS: Container@TILE_WIDGETS:
Logic: TileSelectorLogic Logic: TileSelectorLogic
X: WINDOW_RIGHT - 255 X: WINDOW_RIGHT - 295
Y: 278 Y: 318
Width: 250 Width: 290
Height: WINDOW_BOTTOM - 370 Height: WINDOW_BOTTOM - 410
ClickThrough: false ClickThrough: false
Children: Children:
Container@TILES_BG: Container@TILES_BG:
@@ -402,10 +408,10 @@ Container@EDITOR_WORLD_ROOT:
Y: 4 Y: 4
Container@LAYER_WIDGETS: Container@LAYER_WIDGETS:
Logic: LayerSelectorLogic Logic: LayerSelectorLogic
X: WINDOW_RIGHT - 255 X: WINDOW_RIGHT - 295
Y: 278 Y: 318
Width: 250 Width: 290
Height: WINDOW_BOTTOM - 370 Height: WINDOW_BOTTOM - 410
ClickThrough: false ClickThrough: false
Children: Children:
Container@LAYERS_BG: Container@LAYERS_BG:
@@ -429,10 +435,10 @@ Container@EDITOR_WORLD_ROOT:
Visible: false Visible: false
Container@ACTOR_WIDGETS: Container@ACTOR_WIDGETS:
Logic: ActorSelectorLogic Logic: ActorSelectorLogic
X: WINDOW_RIGHT - 255 X: WINDOW_RIGHT - 295
Y: 278 Y: 318
Width: 250 Width: 290
Height: WINDOW_BOTTOM - 370 Height: WINDOW_BOTTOM - 410
ClickThrough: false ClickThrough: false
Children: Children:
Container@ACTORS_BG: Container@ACTORS_BG:
@@ -499,33 +505,72 @@ Container@EDITOR_WORLD_ROOT:
X: 4 X: 4
Y: 4 Y: 4
Visible: true Visible: true
Container@HISTORY_WIDGETS:
Logic: HistoryLogLogic
X: WINDOW_RIGHT - 295
Y: 318
Width: 290
Height: WINDOW_BOTTOM - 410
ClickThrough: false
Children:
Container@HISTORY_BG:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
ScrollPanel@HISTORY_LIST:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
CollapseHiddenChildren: True
TopBottomSpacing: 4
ItemSpacing: 4
Children:
ScrollItem@HISTORY_TEMPLATE:
X: 4
Visible: false
Width: PARENT_RIGHT - 31
Height: 25
IgnoreChildMouseOver: true
TextColor: ffffff
TextColorDisabled: 8f8f8f
Children:
Label@TITLE:
X: 5
Width: PARENT_RIGHT
Height: 25
Align: Left
Container@MAP_EDITOR_TAB_CONTAINER: Container@MAP_EDITOR_TAB_CONTAINER:
Logic: MapEditorTabsLogic Logic: MapEditorTabsLogic
X: WINDOW_RIGHT - 255 X: WINDOW_RIGHT - 295
Y: 254 Y: 294
Width: 250 Width: 290
Height: 25 Height: 25
ClickThrough: false ClickThrough: false
Children: Children:
Button@TILES_TAB: Button@TILES_TAB:
Width: 81 Width: 71
Height: 25 Height: 25
Text: Tiles Text: Tiles
Font: Bold Font: Bold
Button@OVERLAYS_TAB: Button@OVERLAYS_TAB:
X: 80 X: 70
Width: 90 Width: 80
Height: 25 Height: 25
Text: Overlays Text: Overlays
Font: Bold Font: Bold
Button@ACTORS_TAB: Button@ACTORS_TAB:
X: 169 X: 149
Width: 81 Width: 71
Height: 25 Height: 25
Text: Actors Text: Actors
Font: Bold Font: Bold
Button@HISTORY_TAB:
X: 219
Width: 71
Height: 25
Text: History
Font: Bold
Button@GRID_BUTTON: Button@GRID_BUTTON:
X: WINDOW_RIGHT - 650 X: WINDOW_RIGHT - 690
Y: 5 Y: 5
Width: 100 Width: 100
Height: 25 Height: 25
@@ -536,7 +581,7 @@ Container@EDITOR_WORLD_ROOT:
TooltipText: Toggle the terrain grid TooltipText: Toggle the terrain grid
TooltipContainer: TOOLTIP_CONTAINER TooltipContainer: TOOLTIP_CONTAINER
Label@ZOOM_LABEL: Label@ZOOM_LABEL:
X: WINDOW_RIGHT - 730 - 55 X: WINDOW_RIGHT - 770 - 55
Y: 5 Y: 5
Width: 50 Width: 50
Height: 25 Height: 25
@@ -545,7 +590,7 @@ Container@EDITOR_WORLD_ROOT:
Font: Bold Font: Bold
Contrast: true Contrast: true
DropDownButton@ZOOM_BUTTON: DropDownButton@ZOOM_BUTTON:
X: WINDOW_RIGHT - 730 X: WINDOW_RIGHT - 770
Y: 5 Y: 5
Width: 70 Width: 70
Height: 25 Height: 25
@@ -555,7 +600,7 @@ Container@EDITOR_WORLD_ROOT:
TooltipText: Zoom TooltipText: Zoom
TooltipContainer: TOOLTIP_CONTAINER TooltipContainer: TOOLTIP_CONTAINER
Button@COPYPASTE_BUTTON: Button@COPYPASTE_BUTTON:
X: WINDOW_RIGHT - 540 X: WINDOW_RIGHT - 580
Y: 5 Y: 5
Width: 96 Width: 96
Height: 25 Height: 25
@@ -565,7 +610,7 @@ Container@EDITOR_WORLD_ROOT:
TooltipText: Copy TooltipText: Copy
TooltipContainer: TOOLTIP_CONTAINER TooltipContainer: TOOLTIP_CONTAINER
DropDownButton@COPYFILTER_BUTTON: DropDownButton@COPYFILTER_BUTTON:
X: WINDOW_RIGHT - 435 X: WINDOW_RIGHT - 475
Y: 5 Y: 5
Width: 140 Width: 140
Height: 25 Height: 25
@@ -585,6 +630,26 @@ Container@EDITOR_WORLD_ROOT:
Align: Left Align: Left
Font: Bold Font: Bold
Contrast: true Contrast: true
Button@UNDO_BUTTON:
X: 200
Height: 25
Width: 100
Text: Undo
Font: Bold
Key: z ctrl
TooltipTemplate: BUTTON_TOOLTIP
TooltipText: Undo last step
TooltipContainer: TOOLTIP_CONTAINER
Button@REDO_BUTTON:
X: 305
Height: 25
Width: 100
Text: Redo
Font: Bold
Key: y ctrl
TooltipTemplate: BUTTON_TOOLTIP
TooltipText: Redo last step
TooltipContainer: TOOLTIP_CONTAINER
ScrollPanel@CATEGORY_FILTER_PANEL: ScrollPanel@CATEGORY_FILTER_PANEL:
Width: 190 Width: 190

View File

@@ -268,3 +268,4 @@ EditorWorld:
EditorResourceLayer: EditorResourceLayer:
EditorSelectionLayer: EditorSelectionLayer:
LoadWidgetAtGameStart: LoadWidgetAtGameStart:
EditorActionManager:

View File

@@ -287,15 +287,21 @@ Container@EDITOR_WORLD_ROOT:
Children: Children:
Button@DELETE_BUTTON: Button@DELETE_BUTTON:
X: 15 X: 15
Width: 85 Width: 75
Height: 25 Height: 25
Text: Delete Text: Delete
Font: Bold Font: Bold
Button@CLOSE_BUTTON: Button@CANCEL_BUTTON:
X: 195 X: 125
Width: 85 Width: 75
Height: 25 Height: 25
Text: Close Text: Cancel
Font: Bold
Button@OK_BUTTON:
X: 205
Width: 75
Height: 25
Text: OK
Font: Bold Font: Bold
ViewportController: ViewportController:
Width: WINDOW_RIGHT Width: WINDOW_RIGHT
@@ -313,10 +319,10 @@ Container@EDITOR_WORLD_ROOT:
BookmarkRestoreKeyPrefix: MapBookmarkRestore BookmarkRestoreKeyPrefix: MapBookmarkRestore
BookmarkKeyCount: 4 BookmarkKeyCount: 4
Background@RADAR_BG: Background@RADAR_BG:
X: WINDOW_RIGHT - 255 X: WINDOW_RIGHT - 325
Y: 5 Y: 5
Width: 250 Width: 320
Height: 250 Height: 320
Children: Children:
Radar@INGAME_RADAR: Radar@INGAME_RADAR:
X: 10 X: 10
@@ -327,10 +333,10 @@ Container@EDITOR_WORLD_ROOT:
Logic: TileSelectorLogic Logic: TileSelectorLogic
Children: Children:
Background@TILES_BG: Background@TILES_BG:
X: WINDOW_RIGHT - 250 X: WINDOW_RIGHT - 320
Y: 290 Y: 360
Width: 240 Width: 310
Height: WINDOW_BOTTOM - 382 Height: WINDOW_BOTTOM - 452
Children: Children:
Label@SEARCH_LABEL: Label@SEARCH_LABEL:
Y: 12 Y: 12
@@ -378,10 +384,10 @@ Container@EDITOR_WORLD_ROOT:
Logic: LayerSelectorLogic Logic: LayerSelectorLogic
Children: Children:
Background@LAYERS_BG: Background@LAYERS_BG:
X: WINDOW_RIGHT - 250 X: WINDOW_RIGHT - 320
Y: 290 Y: 360
Width: 240 Width: 310
Height: WINDOW_BOTTOM - 382 Height: WINDOW_BOTTOM - 452
Children: Children:
ScrollPanel@LAYERTEMPLATE_LIST: ScrollPanel@LAYERTEMPLATE_LIST:
X: 10 X: 10
@@ -405,10 +411,10 @@ Container@EDITOR_WORLD_ROOT:
Logic: ActorSelectorLogic Logic: ActorSelectorLogic
Children: Children:
Background@ACTORS_BG: Background@ACTORS_BG:
X: WINDOW_RIGHT - 250 X: WINDOW_RIGHT - 320
Y: 290 Y: 360
Width: 240 Width: 310
Height: WINDOW_BOTTOM - 382 Height: WINDOW_BOTTOM - 452
Children: Children:
Label@SEARCH_LABEL: Label@SEARCH_LABEL:
Y: 12 Y: 12
@@ -467,11 +473,44 @@ Container@EDITOR_WORLD_ROOT:
X: 4 X: 4
Y: 4 Y: 4
Visible: true Visible: true
Container@HISTORY_WIDGETS:
Logic: HistoryLogLogic
Visible: false
Children:
Background@HISTORY_BG:
X: WINDOW_RIGHT - 320
Y: 360
Width: 310
Height: WINDOW_BOTTOM - 452
Children:
ScrollPanel@HISTORY_LIST:
X: 10
Y: 10
Width: PARENT_RIGHT - 20
Height: PARENT_BOTTOM - 20
CollapseHiddenChildren: True
TopBottomSpacing: 4
ItemSpacing: 4
Children:
ScrollItem@HISTORY_TEMPLATE:
X: 4
Visible: false
Width: PARENT_RIGHT - 31
Height: 25
IgnoreChildMouseOver: true
TextColor: ffffff
TextColorDisabled: 8f8f8f
Children:
Label@TITLE:
X: 5
Width: PARENT_RIGHT
Height: 25
Align: Left
Container@MAP_EDITOR_TAB_CONTAINER: Container@MAP_EDITOR_TAB_CONTAINER:
Logic: MapEditorTabsLogic Logic: MapEditorTabsLogic
X: WINDOW_RIGHT - 245 X: WINDOW_RIGHT - 315
Y: 260 Y: 330
Width: 240 Width: 310
Height: 25 Height: 25
Children: Children:
Button@TILES_TAB: Button@TILES_TAB:
@@ -492,6 +531,12 @@ Container@EDITOR_WORLD_ROOT:
Height: 25 Height: 25
Text: Actors Text: Actors
Font: Bold Font: Bold
Button@HISTORY_TAB:
X: 230
Width: 70
Height: 25
Text: History
Font: Bold
MenuButton@OPTIONS_BUTTON: MenuButton@OPTIONS_BUTTON:
Logic: MenuButtonsChromeLogic Logic: MenuButtonsChromeLogic
MenuContainer: INGAME_MENU MenuContainer: INGAME_MENU
@@ -547,15 +592,35 @@ Container@EDITOR_WORLD_ROOT:
TooltipTemplate: BUTTON_TOOLTIP TooltipTemplate: BUTTON_TOOLTIP
TooltipText: Zoom TooltipText: Zoom
TooltipContainer: TOOLTIP_CONTAINER TooltipContainer: TOOLTIP_CONTAINER
Button@UNDO_BUTTON:
X: 630
Height: 25
Width: 90
Text: Undo
Font: Bold
Key: z ctrl
TooltipTemplate: BUTTON_TOOLTIP
TooltipText: Undo last step
TooltipContainer: TOOLTIP_CONTAINER
Button@REDO_BUTTON:
X: 730
Height: 25
Width: 90
Text: Redo
Font: Bold
Key: y ctrl
TooltipTemplate: BUTTON_TOOLTIP
TooltipText: Redo last step
TooltipContainer: TOOLTIP_CONTAINER
Label@COORDINATE_LABEL: Label@COORDINATE_LABEL:
X: 635 X: 835
Width: 50 Width: 50
Height: 25 Height: 25
Align: Left Align: Left
Font: Bold Font: Bold
Contrast: true Contrast: true
Label@CASH_LABEL: Label@CASH_LABEL:
X: 750 X: 950
Width: 50 Width: 50
Height: 25 Height: 25
Align: Left Align: Left

View File

@@ -243,3 +243,4 @@ EditorWorld:
D2kEditorResourceLayer: D2kEditorResourceLayer:
EditorSelectionLayer: EditorSelectionLayer:
LoadWidgetAtGameStart: LoadWidgetAtGameStart:
EditorActionManager:

View File

@@ -284,3 +284,4 @@ EditorWorld:
EditorResourceLayer: EditorResourceLayer:
EditorSelectionLayer: EditorSelectionLayer:
LoadWidgetAtGameStart: LoadWidgetAtGameStart:
EditorActionManager:

View File

@@ -379,3 +379,4 @@ EditorWorld:
EditorSelectionLayer: EditorSelectionLayer:
Palette: placefootprint Palette: placefootprint
LoadWidgetAtGameStart: LoadWidgetAtGameStart:
EditorActionManager: