From 76034c198ef72089e9ece7f04f4d383b417eadda Mon Sep 17 00:00:00 2001 From: teinarss Date: Sat, 13 Jul 2019 18:21:01 +0200 Subject: [PATCH] Added Undo Redo to editor --- .../EditorBrushes/EditorActorBrush.cs | 96 ++++++-- .../EditorBrushes/EditorCopyPasteBrush.cs | 135 ++++++++-- .../EditorBrushes/EditorDefaultBrush.cs | 75 +++++- .../EditorBrushes/EditorResourceBrush.cs | 77 +++++- .../EditorBrushes/EditorTileBrush.cs | 109 +++++++-- .../Traits/World/EditorActionManager.cs | 172 +++++++++++++ .../Traits/World/EditorActorLayer.cs | 15 +- .../Traits/World/EditorActorPreview.cs | 53 +++- .../Widgets/EditorViewportControllerWidget.cs | 16 ++ .../Widgets/Logic/Editor/ActorEditLogic.cs | 230 +++++++++++++++++- .../Widgets/Logic/Editor/HistoryLogLogic.cs | 71 ++++++ .../Widgets/Logic/Editor/MapEditorLogic.cs | 15 ++ .../Logic/Editor/MapEditorTabsLogic.cs | 3 +- mods/cnc/chrome/editor.yaml | 133 +++++++--- mods/cnc/rules/world.yaml | 1 + mods/common/chrome/editor.yaml | 115 +++++++-- mods/d2k/rules/world.yaml | 1 + mods/ra/rules/world.yaml | 1 + mods/ts/rules/world.yaml | 1 + 19 files changed, 1155 insertions(+), 164 deletions(-) create mode 100644 OpenRA.Mods.Common/Traits/World/EditorActionManager.cs create mode 100644 OpenRA.Mods.Common/Widgets/Logic/Editor/HistoryLogLogic.cs diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs index 52b872e7d2..8f24174f71 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs @@ -24,6 +24,7 @@ namespace OpenRA.Mods.Common.Widgets readonly WorldRenderer worldRenderer; readonly World world; readonly EditorActorLayer editorLayer; + readonly EditorActionManager editorActionManager; readonly EditorViewportControllerWidget editorWidget; readonly ActorPreviewWidget preview; readonly WVec centerOffset; @@ -38,6 +39,7 @@ namespace OpenRA.Mods.Common.Widgets worldRenderer = wr; world = wr.World; editorLayer = world.WorldActor.Trait(); + editorActionManager = world.WorldActor.Trait(); Actor = actor; this.owner = owner; @@ -101,33 +103,8 @@ namespace OpenRA.Mods.Common.Widgets return true; // Enforce first entry of ValidOwnerNames as owner if the actor has RequiresSpecificOwners - var ownerName = owner.Name; - var specificOwnerInfo = Actor.TraitInfoOrDefault(); - 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(); - 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()) - initDict.Add(new FacingInit(facing)); - - if (Actor.HasTraitInfo()) - initDict.Add(new TurretFacingInit(facing)); - - editorLayer.Add(newActorReference); + var action = new AddActorAction(editorLayer, Actor, cell, owner, facing); + editorActionManager.Add(action); } return true; @@ -151,4 +128,69 @@ namespace OpenRA.Mods.Common.Widgets 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(); + 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(); + 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()) + initDict.Add(new FacingInit(facing)); + + if (actor.HasTraitInfo()) + 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); + } + } } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs index 2e9e4b2e65..33552b82a2 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs @@ -36,6 +36,7 @@ namespace OpenRA.Mods.Common.Widgets readonly EditorSelectionLayer selectionLayer; readonly EditorActorLayer editorLayer; readonly Func getCopyFilters; + readonly EditorActionManager editorActionManager; State state; CPos start; @@ -46,6 +47,8 @@ namespace OpenRA.Mods.Common.Widgets this.editorWidget = editorWidget; worldRenderer = wr; + editorActionManager = wr.World.WorldActor.Trait(); + selectionLayer = wr.World.WorldActor.Trait(); editorLayer = wr.World.WorldActor.Trait(); this.getCopyFilters = getCopyFilters; @@ -143,26 +146,8 @@ namespace OpenRA.Mods.Common.Widgets } } - foreach (var kv in tiles) - { - 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); + var action = new CopyPasteEditorAction(copyFilters, worldRenderer.World.Map, tiles, previews, editorLayer, dest); + editorActionManager.Add(action); } public void Tick() @@ -187,4 +172,114 @@ namespace OpenRA.Mods.Common.Widgets selectionLayer.Clear(); } } + + class CopyPasteEditorAction : IEditorAction + { + public string Text { get; private set; } + + readonly MapCopyFilters copyFilters; + readonly Dictionary> tiles; + readonly Dictionary previews; + readonly EditorActorLayer editorLayer; + readonly CellRegion dest; + readonly CellLayer mapTiles; + readonly CellLayer mapHeight; + readonly CellLayer mapResources; + + readonly Queue undoCopyPastes = new Queue(); + readonly Queue removedActors = new Queue(); + readonly Queue addedActorPreviews = new Queue(); + + public CopyPasteEditorAction(MapCopyFilters copyFilters, Map map, + Dictionary> tiles, Dictionary 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; + } + } } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs index dffe3b341f..affcc63847 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs @@ -33,6 +33,8 @@ namespace OpenRA.Mods.Common.Widgets readonly EditorViewportControllerWidget editorWidget; readonly EditorActorLayer editorLayer; readonly Dictionary resources; + readonly EditorActionManager editorActionManager; + public EditorActorPreview SelectedActor; int2 worldPixel; @@ -45,6 +47,8 @@ namespace OpenRA.Mods.Common.Widgets editorLayer = world.WorldActor.Trait(); resources = world.WorldActor.TraitsImplementing() .ToDictionary(r => r.Info.ResourceType, r => r); + + editorActionManager = world.WorldActor.Trait(); } long CalculateActorSelectionPriority(EditorActorPreview actor) @@ -74,7 +78,7 @@ namespace OpenRA.Mods.Common.Widgets var underCursor = editorLayer.PreviewsAt(worldPixel).MinByOrDefault(CalculateActorSelectionPriority); var mapResources = world.Map.Resources; - ResourceType type; + ResourceType type = null; if (underCursor != null) editorWidget.SetTooltip(underCursor.Tooltip); else if (mapResources.Contains(cell) && resources.TryGetValue(mapResources[cell].Type, out type)) @@ -97,10 +101,10 @@ namespace OpenRA.Mods.Common.Widgets editorWidget.SetTooltip(null); if (underCursor != null && underCursor != SelectedActor) - editorLayer.Remove(underCursor); + editorActionManager.Add(new RemoveActorAction(editorLayer, underCursor)); if (mapResources.Contains(cell) && mapResources[cell].Type != 0) - mapResources[cell] = default(ResourceTile); + editorActionManager.Add(new RemoveResourceAction(mapResources, cell, type)); } return true; @@ -109,4 +113,69 @@ namespace OpenRA.Mods.Common.Widgets public void Tick() { } 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 mapResources; + readonly CPos cell; + + ResourceTile resourceTile; + + public RemoveResourceAction(CellLayer 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; + } + } } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs index a5a8847cc7..61b12b6305 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs @@ -9,6 +9,7 @@ */ #endregion +using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Traits; @@ -23,6 +24,10 @@ namespace OpenRA.Mods.Common.Widgets readonly World world; readonly EditorViewportControllerWidget editorWidget; readonly SpriteWidget preview; + readonly EditorActionManager editorActionManager; + + AddResourcesEditorAction action; + bool resourceAdded; public EditorResourceBrush(EditorViewportControllerWidget editorWidget, ResourceTypeInfo resource, WorldRenderer wr) { @@ -30,6 +35,8 @@ namespace OpenRA.Mods.Common.Widgets ResourceType = resource; worldRenderer = wr; world = wr.World; + editorActionManager = world.WorldActor.Trait(); + action = new AddResourcesEditorAction(world.Map, ResourceType); preview = editorWidget.Get("DRAG_LAYER_PREVIEW"); preview.Palette = resource.Palette; @@ -65,11 +72,18 @@ namespace OpenRA.Mods.Common.Widgets 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 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; @@ -112,4 +126,63 @@ namespace OpenRA.Mods.Common.Widgets 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 cellResources = new List(); + + 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); + } + } } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs index 55120fbca6..998d595c37 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; +using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; namespace OpenRA.Mods.Common.Widgets @@ -26,6 +27,7 @@ namespace OpenRA.Mods.Common.Widgets readonly EditorViewportControllerWidget editorWidget; readonly TerrainTemplatePreviewWidget preview; readonly Rectangle bounds; + readonly EditorActionManager editorActionManager; bool painting; @@ -36,6 +38,8 @@ namespace OpenRA.Mods.Common.Widgets worldRenderer = wr; world = wr.World; + editorActionManager = world.WorldActor.Trait(); + preview = editorWidget.Get("DRAG_TILE_PREVIEW"); preview.GetScale = () => worldRenderer.Viewport.Zoom; preview.IsVisible = () => editorWidget.CurrentBrush == this; @@ -97,33 +101,13 @@ namespace OpenRA.Mods.Common.Widgets void PaintCell(CPos cell, bool isMoving) { var map = world.Map; - var mapTiles = map.Tiles; - var mapHeight = map.Height; - var tileset = map.Rules.TileSet; var template = tileset.Templates[Template]; - var baseHeight = mapHeight.Contains(cell) ? mapHeight[cell] : (byte)0; if (isMoving && PlacementOverlapsSameTemplate(template, cell)) return; - var i = 0; - 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); - } - } - } + editorActionManager.Add(new PaintTileEditorAction(Template, map, cell)); } void FloodFillWithBrush(CPos cell, bool isMoving) @@ -239,4 +223,87 @@ namespace OpenRA.Mods.Common.Widgets public void Dispose() { } } + + class PaintTileEditorAction : IEditorAction + { + public string Text { get; private set; } + + readonly ushort template; + readonly Map map; + readonly CPos cell; + + readonly Queue undoTiles = new Queue(); + 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; + } + } } diff --git a/OpenRA.Mods.Common/Traits/World/EditorActionManager.cs b/OpenRA.Mods.Common/Traits/World/EditorActionManager.cs new file mode 100644 index 0000000000..3554bdd436 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/World/EditorActionManager.cs @@ -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 { } + + public class EditorActionManager : IWorldLoaded + { + readonly Stack undoStack = new Stack(); + readonly Stack redoStack = new Stack(); + + public event Action ItemAdded; + public event Action 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; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs index 72716801ca..dd86d3bf25 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs @@ -110,8 +110,15 @@ namespace OpenRA.Mods.Common.Traits var owner = Players.Players[reference.InitDict.Get().PlayerName]; 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) screenMap.Add(preview, preview.Bounds); @@ -126,11 +133,9 @@ namespace OpenRA.Mods.Common.Traits { UpdateNeighbours(preview.Footprint); - if (reference.Type == "mpspawn") + if (preview.Actor.Type == "mpspawn") SyncMultiplayerCount(); } - - return preview; } public void Remove(EditorActorPreview preview) @@ -262,7 +267,7 @@ namespace OpenRA.Mods.Common.Traits string NextActorName() { - var id = previews.Count(); + var id = previews.Count; var possibleName = "Actor" + id.ToString(); while (previews.Any(p => p.ID == possibleName)) diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs index 942fdbf2a3..49a6cff8ea 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorPreview.cs @@ -20,7 +20,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - public class EditorActorPreview + public class EditorActorPreview : IEquatable { public readonly string DescriptiveName; public readonly ActorInfo Info; @@ -28,6 +28,7 @@ namespace OpenRA.Mods.Common.Traits public readonly IReadOnlyDictionary Footprint; public readonly Rectangle Bounds; public readonly SelectionBoxRenderable SelectionBox; + public readonly ActorReference Actor; public string Tooltip { @@ -43,7 +44,6 @@ namespace OpenRA.Mods.Common.Traits public SubCell SubCell { get; private set; } public bool Selected { get; set; } - readonly ActorReference actor; readonly WorldRenderer worldRenderer; readonly TooltipInfoBase tooltip; IActorPreview[] previews; @@ -51,7 +51,7 @@ namespace OpenRA.Mods.Common.Traits public EditorActorPreview(WorldRenderer worldRenderer, string id, ActorReference actor, PlayerReference owner) { ID = id; - this.actor = actor; + Actor = actor; Owner = owner; this.worldRenderer = worldRenderer; @@ -120,25 +120,25 @@ namespace OpenRA.Mods.Common.Traits public void ReplaceInit(T init) { - var original = actor.InitDict.GetOrDefault(); + var original = Actor.InitDict.GetOrDefault(); if (original != null) - actor.InitDict.Remove(original); + Actor.InitDict.Remove(original); - actor.InitDict.Add(init); + Actor.InitDict.Add(init); GeneratePreviews(); } public void RemoveInit() { - var original = actor.InitDict.GetOrDefault(); + var original = Actor.InitDict.GetOrDefault(); if (original != null) - actor.InitDict.Remove(original); + Actor.InitDict.Remove(original); GeneratePreviews(); } public T Init() { - return actor.InitDict.GetOrDefault(); + return Actor.InitDict.GetOrDefault(); } public MiniYaml Save() @@ -154,7 +154,7 @@ namespace OpenRA.Mods.Common.Traits return true; }; - return actor.Save(saveInit); + return Actor.Save(saveInit); } WPos PreviewPosition(World world, TypeDictionary init) @@ -167,7 +167,7 @@ namespace OpenRA.Mods.Common.Traits var cell = init.Get().Value(world); var offset = WVec.Zero; - var subCellInit = actor.InitDict.GetOrDefault(); + var subCellInit = Actor.InitDict.GetOrDefault(); var subCell = subCellInit != null ? subCellInit.Value(worldRenderer.World) : SubCell.Any; var buildingInfo = Info.TraitInfoOrDefault(); @@ -182,7 +182,7 @@ namespace OpenRA.Mods.Common.Traits void GeneratePreviews() { - var init = new ActorPreviewInitializer(Info, worldRenderer, actor.InitDict); + var init = new ActorPreviewInitializer(Info, worldRenderer, Actor.InitDict); previews = Info.TraitInfos() .SelectMany(rpi => rpi.RenderPreview(init)) .ToArray(); @@ -190,12 +190,39 @@ namespace OpenRA.Mods.Common.Traits public ActorReference Export() { - return new ActorReference(actor.Type, actor.Save().ToDictionary()); + return new ActorReference(Actor.Type, Actor.Save().ToDictionary()); } public override string ToString() { 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; + } } } diff --git a/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs b/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs index 5a0c16a630..329571fd33 100644 --- a/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/EditorViewportControllerWidget.cs @@ -11,6 +11,7 @@ using System; using OpenRA.Graphics; +using OpenRA.Mods.Common.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets @@ -25,6 +26,7 @@ namespace OpenRA.Mods.Common.Widgets readonly Lazy tooltipContainer; readonly WorldRenderer worldRenderer; + readonly EditorActionManager editorActionManager; bool enableTooltips; @@ -34,6 +36,14 @@ namespace OpenRA.Mods.Common.Widgets this.worldRenderer = worldRenderer; tooltipContainer = Exts.Lazy(() => Ui.Root.Get(TooltipContainer)); CurrentBrush = DefaultBrush = new EditorDefaultBrush(this, worldRenderer); + editorActionManager = world.WorldActor.Trait(); + + editorActionManager.OnChange += EditorActionManagerOnChange; + } + + void EditorActionManagerOnChange() + { + DefaultBrush.SelectedActor = null; } public void ClearBrush() { SetBrush(null); } @@ -109,5 +119,11 @@ namespace OpenRA.Mods.Common.Widgets cachedViewportPosition = worldRenderer.Viewport.CenterPosition; CurrentBrush.Tick(); } + + public override void Removed() + { + base.Removed(); + editorActionManager.OnChange -= EditorActionManagerOnChange; + } } } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorEditLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorEditLogic.cs index 5055451083..59e06977de 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorEditLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/ActorEditLogic.cs @@ -26,6 +26,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly WorldRenderer worldRenderer; readonly EditorActorLayer editorActorLayer; + readonly EditorActionManager editorActionManager; readonly EditorViewportControllerWidget editor; readonly BackgroundWidget actorEditPanel; readonly LabelWidget typeLabel; @@ -48,6 +49,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic string initialActorID; EditorActorPreview currentActorInner; + EditActorPreview editActorPreview; + EditorActorPreview CurrentActor { get @@ -61,7 +64,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic return; if (currentActorInner != null) + { + Reset(); currentActorInner.Selected = false; + } currentActorInner = value; if (currentActorInner != null) @@ -74,6 +80,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic { this.worldRenderer = worldRenderer; editorActorLayer = world.WorldActor.Trait(); + editorActionManager = world.WorldActor.Trait(); + editor = widget.Parent.Get("MAP_EDITOR"); actorEditPanel = editor.Get("ACTOR_EDIT_PANEL"); @@ -88,7 +96,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic initContainer.RemoveChildren(); var deleteButton = actorEditPanel.Get("DELETE_BUTTON"); - var closeButton = actorEditPanel.Get("CLOSE_BUTTON"); + var cancelButton = actorEditPanel.Get("CANCEL_BUTTON"); + var okButton = actorEditPanel.Get("OK_BUTTON"); actorIDErrorLabel = actorEditPanel.Get("ACTOR_ID_ERROR_LABEL"); actorIDErrorLabel.IsVisible = () => actorIDStatus != ActorIDStatus.Normal; @@ -99,7 +108,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (logicArgs.TryGetValue("EditPanelPadding", out yaml)) editPanelPadding = FieldLoader.GetValue("EditPanelPadding", yaml.Value); - closeButton.OnClick = Close; + okButton.IsDisabled = () => !IsValid() || !editActorPreview.IsDirty; + okButton.OnClick = Save; + cancelButton.OnClick = Cancel; deleteButton.OnClick = Delete; actorEditPanel.IsVisible = () => CurrentActor != null && editor.CurrentBrush == editor.DefaultBrush @@ -113,15 +124,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic actorIDField.OnTextEdited = () => { - if (string.IsNullOrWhiteSpace(actorIDField.Text)) + var actorId = actorIDField.Text.Trim(); + if (string.IsNullOrWhiteSpace(actorId)) { nextActorIDStatus = ActorIDStatus.Empty; return; } // Check for duplicate actor ID - var actorId = actorIDField.Text.ToLowerInvariant(); - if (CurrentActor.ID.ToLowerInvariant() != actorId) + if (CurrentActor.ID.Equals(actorId, StringComparison.OrdinalIgnoreCase)) { if (editorActorLayer[actorId] != null) { @@ -130,23 +141,30 @@ namespace OpenRA.Mods.Common.Widgets.Logic } } - SetActorID(world, actorId); + SetActorID(actorId); + nextActorIDStatus = ActorIDStatus.Normal; }; actorIDField.OnLoseFocus = () => { // Reset invalid IDs back to their starting value 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; } + bool IsValid() + { + return nextActorIDStatus == ActorIDStatus.Normal; + } + public override void Tick() { if (actorIDStatus != nextActorIDStatus) @@ -183,6 +201,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic lastScrollTime = 0; // Ensure visible CurrentActor = actor; + editActorPreview = new EditActorPreview(CurrentActor); + initialActorID = actorIDField.Text = actor.ID; var font = Game.Renderer.Fonts[typeLabel.Font]; @@ -203,13 +223,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic var ownerDropdown = ownerContainer.Get("OPTION"); var selectedOwner = actor.Owner; + Action updateOwner = (preview, reference) => + { + preview.Owner = reference; + preview.ReplaceInit(new OwnerInit(reference.Name)); + }; + + var ownerHandler = new EditorActorOptionActionHandle(updateOwner, actor.Owner); + editActorPreview.Add(ownerHandler); + Func setupItem = (option, template) => { var item = ScrollItemWidget.Setup(template, () => selectedOwner == option, () => { selectedOwner = option; - CurrentActor.Owner = selectedOwner; - CurrentActor.ReplaceInit(new OwnerInit(selectedOwner.Name)); + updateOwner(CurrentActor, selectedOwner); + ownerHandler.OnChange(option); }); item.Get("LABEL").GetText = () => option.Name; @@ -248,8 +277,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic slider.MaximumValue = so.MaxValue; slider.Ticks = so.Ticks; + var editorActionHandle = new EditorActorOptionActionHandle(so.OnChange, so.GetValue(actor)); + editActorPreview.Add(editorActionHandle); + slider.GetValue = () => so.GetValue(actor); slider.OnChange += value => so.OnChange(actor, value); + slider.OnChange += value => editorActionHandle.OnChange(value); initContainer.AddChild(sliderContainer); } @@ -261,12 +294,19 @@ namespace OpenRA.Mods.Common.Widgets.Logic initContainer.Bounds.Height += dropdownContainer.Bounds.Height; dropdownContainer.Get("LABEL").GetText = () => ddo.Name; + var editorActionHandle = new EditorActorOptionActionHandle(ddo.OnChange, ddo.GetValue(actor)); + editActorPreview.Add(editorActionHandle); + var dropdown = dropdownContainer.Get("OPTION"); Func, ScrollItemWidget, ScrollItemWidget> dropdownSetup = (option, template) => { var item = ScrollItemWidget.Setup(template, () => ddo.GetValue(actor) == option.Key, - () => ddo.OnChange(actor, option.Key)); + () => + { + ddo.OnChange(actor, option.Key); + editorActionHandle.OnChange(option.Key); + }); item.Get("LABEL").GetText = () => option.Value; return item; @@ -298,16 +338,180 @@ namespace OpenRA.Mods.Common.Widgets.Logic void Delete() { if (CurrentActor != null) - editorActorLayer.Remove(CurrentActor); + editorActionManager.Add(new RemoveActorAction(editorActorLayer, CurrentActor)); Close(); } + void Cancel() + { + Reset(); + Close(); + } + + void Reset() + { + if (editActorPreview != null) + editActorPreview.Reset(); + } + void Close() { actorIDField.YieldKeyboardFocus(); editor.DefaultBrush.SelectedActor = null; CurrentActor = null; } + + void Save() + { + editorActionManager.Add(new EditActorEditorAction(editorActorLayer, CurrentActor, editActorPreview.GetDirtyHandles())); + editActorPreview = null; + Close(); + } + } + + public class EditorActorOptionActionHandle : IEditActorHandle + { + readonly Action change; + T value; + readonly T initialValue; + + public EditorActorOptionActionHandle(Action change, T value) + { + this.change = change; + this.value = value; + initialValue = value; + } + + public void OnChange(T value) + { + IsDirty = !EqualityComparer.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 handles; + readonly EditorActorLayer editorActorLayer; + EditorActorPreview actor; + readonly string actorId; + + public EditActorEditorAction(EditorActorLayer editorActorLayer, EditorActorPreview actor, IEnumerable 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 handles = new List(); + + 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 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; } } } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/HistoryLogLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/HistoryLogLogic.cs new file mode 100644 index 0000000000..1203750e0a --- /dev/null +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/HistoryLogLogic.cs @@ -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 states = new Dictionary(); + + [ObjectCreator.UseCtor] + public HistoryLogLogic(Widget widget, World world, WorldRenderer worldRenderer, Dictionary logicArgs) + { + panel = widget.Get("HISTORY_LIST"); + template = panel.Get("HISTORY_TEMPLATE"); + editorActionManager = world.WorldActor.Trait(); + + 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("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); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs index 927da47b37..b3ccf77b49 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs @@ -128,6 +128,21 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (reslayer != null) cashLabel.GetText = () => "$ {0}".F(reslayer.NetWorth); } + + var actionManager = world.WorldActor.Trait(); + var undoButton = widget.GetOrNull("UNDO_BUTTON"); + if (undoButton != null) + { + undoButton.IsDisabled = () => !actionManager.HasUndos(); + undoButton.OnClick = () => actionManager.Undo(); + } + + var redoButton = widget.GetOrNull("REDO_BUTTON"); + if (redoButton != null) + { + redoButton.IsDisabled = () => !actionManager.HasRedos(); + redoButton.OnClick = () => actionManager.Redo(); + } } Widget CreateCategoriesPanel() diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorTabsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorTabsLogic.cs index dfcab16e0d..e6b8760b49 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorTabsLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorTabsLogic.cs @@ -18,7 +18,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { readonly Widget widget; - protected enum MenuType { Tiles, Layers, Actors } + protected enum MenuType { Tiles, Layers, Actors, History } protected MenuType menuType = MenuType.Tiles; readonly Widget tabContainer; @@ -31,6 +31,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic SetupTab("TILES_TAB", "TILE_WIDGETS", MenuType.Tiles); SetupTab("OVERLAYS_TAB", "LAYER_WIDGETS", MenuType.Layers); SetupTab("ACTORS_TAB", "ACTOR_WIDGETS", MenuType.Actors); + SetupTab("HISTORY_TAB", "HISTORY_WIDGETS", MenuType.History); } void SetupTab(string buttonId, string tabId, MenuType tabType) diff --git a/mods/cnc/chrome/editor.yaml b/mods/cnc/chrome/editor.yaml index ab37cbe919..f345fd774f 100644 --- a/mods/cnc/chrome/editor.yaml +++ b/mods/cnc/chrome/editor.yaml @@ -292,15 +292,21 @@ Container@EDITOR_WORLD_ROOT: Children: Button@DELETE_BUTTON: X: 4 - Width: 85 + Width: 75 Height: 25 Text: Delete Font: Bold - Button@CLOSE_BUTTON: - X: 180 - Width: 85 + Button@CANCEL_BUTTON: + X: 110 + Width: 75 Height: 25 - Text: Close + Text: Cancel + Font: Bold + Button@OK_BUTTON: + X: 190 + Width: 75 + Height: 25 + Text: OK Font: Bold ViewportController: Width: WINDOW_RIGHT @@ -318,10 +324,10 @@ Container@EDITOR_WORLD_ROOT: BookmarkRestoreKeyPrefix: MapBookmarkRestore BookmarkKeyCount: 4 Background@RADAR_BG: - X: WINDOW_RIGHT - 255 + X: WINDOW_RIGHT - 295 Y: 5 - Width: 250 - Height: 250 + Width: 290 + Height: 290 Background: panel-gray Children: Radar@INGAME_RADAR: @@ -332,7 +338,7 @@ Container@EDITOR_WORLD_ROOT: MenuButton@OPTIONS_BUTTON: Logic: MenuButtonsChromeLogic Key: escape - X: WINDOW_RIGHT - 254 - WIDTH + X: WINDOW_RIGHT - 294 - WIDTH Y: 5 Width: 30 Height: 25 @@ -346,10 +352,10 @@ Container@EDITOR_WORLD_ROOT: ImageName: options Container@TILE_WIDGETS: Logic: TileSelectorLogic - X: WINDOW_RIGHT - 255 - Y: 278 - Width: 250 - Height: WINDOW_BOTTOM - 370 + X: WINDOW_RIGHT - 295 + Y: 318 + Width: 290 + Height: WINDOW_BOTTOM - 410 ClickThrough: false Children: Container@TILES_BG: @@ -402,10 +408,10 @@ Container@EDITOR_WORLD_ROOT: Y: 4 Container@LAYER_WIDGETS: Logic: LayerSelectorLogic - X: WINDOW_RIGHT - 255 - Y: 278 - Width: 250 - Height: WINDOW_BOTTOM - 370 + X: WINDOW_RIGHT - 295 + Y: 318 + Width: 290 + Height: WINDOW_BOTTOM - 410 ClickThrough: false Children: Container@LAYERS_BG: @@ -429,10 +435,10 @@ Container@EDITOR_WORLD_ROOT: Visible: false Container@ACTOR_WIDGETS: Logic: ActorSelectorLogic - X: WINDOW_RIGHT - 255 - Y: 278 - Width: 250 - Height: WINDOW_BOTTOM - 370 + X: WINDOW_RIGHT - 295 + Y: 318 + Width: 290 + Height: WINDOW_BOTTOM - 410 ClickThrough: false Children: Container@ACTORS_BG: @@ -499,33 +505,72 @@ Container@EDITOR_WORLD_ROOT: X: 4 Y: 4 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: Logic: MapEditorTabsLogic - X: WINDOW_RIGHT - 255 - Y: 254 - Width: 250 + X: WINDOW_RIGHT - 295 + Y: 294 + Width: 290 Height: 25 ClickThrough: false Children: Button@TILES_TAB: - Width: 81 + Width: 71 Height: 25 Text: Tiles Font: Bold Button@OVERLAYS_TAB: - X: 80 - Width: 90 + X: 70 + Width: 80 Height: 25 Text: Overlays Font: Bold Button@ACTORS_TAB: - X: 169 - Width: 81 + X: 149 + Width: 71 Height: 25 Text: Actors Font: Bold + Button@HISTORY_TAB: + X: 219 + Width: 71 + Height: 25 + Text: History + Font: Bold Button@GRID_BUTTON: - X: WINDOW_RIGHT - 650 + X: WINDOW_RIGHT - 690 Y: 5 Width: 100 Height: 25 @@ -536,7 +581,7 @@ Container@EDITOR_WORLD_ROOT: TooltipText: Toggle the terrain grid TooltipContainer: TOOLTIP_CONTAINER Label@ZOOM_LABEL: - X: WINDOW_RIGHT - 730 - 55 + X: WINDOW_RIGHT - 770 - 55 Y: 5 Width: 50 Height: 25 @@ -545,7 +590,7 @@ Container@EDITOR_WORLD_ROOT: Font: Bold Contrast: true DropDownButton@ZOOM_BUTTON: - X: WINDOW_RIGHT - 730 + X: WINDOW_RIGHT - 770 Y: 5 Width: 70 Height: 25 @@ -555,7 +600,7 @@ Container@EDITOR_WORLD_ROOT: TooltipText: Zoom TooltipContainer: TOOLTIP_CONTAINER Button@COPYPASTE_BUTTON: - X: WINDOW_RIGHT - 540 + X: WINDOW_RIGHT - 580 Y: 5 Width: 96 Height: 25 @@ -565,7 +610,7 @@ Container@EDITOR_WORLD_ROOT: TooltipText: Copy TooltipContainer: TOOLTIP_CONTAINER DropDownButton@COPYFILTER_BUTTON: - X: WINDOW_RIGHT - 435 + X: WINDOW_RIGHT - 475 Y: 5 Width: 140 Height: 25 @@ -585,6 +630,26 @@ Container@EDITOR_WORLD_ROOT: Align: Left Font: Bold 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: Width: 190 diff --git a/mods/cnc/rules/world.yaml b/mods/cnc/rules/world.yaml index ad35418cc0..911f7dfc34 100644 --- a/mods/cnc/rules/world.yaml +++ b/mods/cnc/rules/world.yaml @@ -268,3 +268,4 @@ EditorWorld: EditorResourceLayer: EditorSelectionLayer: LoadWidgetAtGameStart: + EditorActionManager: diff --git a/mods/common/chrome/editor.yaml b/mods/common/chrome/editor.yaml index 644d4d3c08..6d12e859e9 100644 --- a/mods/common/chrome/editor.yaml +++ b/mods/common/chrome/editor.yaml @@ -287,15 +287,21 @@ Container@EDITOR_WORLD_ROOT: Children: Button@DELETE_BUTTON: X: 15 - Width: 85 + Width: 75 Height: 25 Text: Delete Font: Bold - Button@CLOSE_BUTTON: - X: 195 - Width: 85 + Button@CANCEL_BUTTON: + X: 125 + Width: 75 Height: 25 - Text: Close + Text: Cancel + Font: Bold + Button@OK_BUTTON: + X: 205 + Width: 75 + Height: 25 + Text: OK Font: Bold ViewportController: Width: WINDOW_RIGHT @@ -313,10 +319,10 @@ Container@EDITOR_WORLD_ROOT: BookmarkRestoreKeyPrefix: MapBookmarkRestore BookmarkKeyCount: 4 Background@RADAR_BG: - X: WINDOW_RIGHT - 255 + X: WINDOW_RIGHT - 325 Y: 5 - Width: 250 - Height: 250 + Width: 320 + Height: 320 Children: Radar@INGAME_RADAR: X: 10 @@ -327,10 +333,10 @@ Container@EDITOR_WORLD_ROOT: Logic: TileSelectorLogic Children: Background@TILES_BG: - X: WINDOW_RIGHT - 250 - Y: 290 - Width: 240 - Height: WINDOW_BOTTOM - 382 + X: WINDOW_RIGHT - 320 + Y: 360 + Width: 310 + Height: WINDOW_BOTTOM - 452 Children: Label@SEARCH_LABEL: Y: 12 @@ -378,10 +384,10 @@ Container@EDITOR_WORLD_ROOT: Logic: LayerSelectorLogic Children: Background@LAYERS_BG: - X: WINDOW_RIGHT - 250 - Y: 290 - Width: 240 - Height: WINDOW_BOTTOM - 382 + X: WINDOW_RIGHT - 320 + Y: 360 + Width: 310 + Height: WINDOW_BOTTOM - 452 Children: ScrollPanel@LAYERTEMPLATE_LIST: X: 10 @@ -405,10 +411,10 @@ Container@EDITOR_WORLD_ROOT: Logic: ActorSelectorLogic Children: Background@ACTORS_BG: - X: WINDOW_RIGHT - 250 - Y: 290 - Width: 240 - Height: WINDOW_BOTTOM - 382 + X: WINDOW_RIGHT - 320 + Y: 360 + Width: 310 + Height: WINDOW_BOTTOM - 452 Children: Label@SEARCH_LABEL: Y: 12 @@ -467,11 +473,44 @@ Container@EDITOR_WORLD_ROOT: X: 4 Y: 4 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: Logic: MapEditorTabsLogic - X: WINDOW_RIGHT - 245 - Y: 260 - Width: 240 + X: WINDOW_RIGHT - 315 + Y: 330 + Width: 310 Height: 25 Children: Button@TILES_TAB: @@ -492,6 +531,12 @@ Container@EDITOR_WORLD_ROOT: Height: 25 Text: Actors Font: Bold + Button@HISTORY_TAB: + X: 230 + Width: 70 + Height: 25 + Text: History + Font: Bold MenuButton@OPTIONS_BUTTON: Logic: MenuButtonsChromeLogic MenuContainer: INGAME_MENU @@ -547,15 +592,35 @@ Container@EDITOR_WORLD_ROOT: TooltipTemplate: BUTTON_TOOLTIP TooltipText: Zoom 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: - X: 635 + X: 835 Width: 50 Height: 25 Align: Left Font: Bold Contrast: true Label@CASH_LABEL: - X: 750 + X: 950 Width: 50 Height: 25 Align: Left diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml index c8dc6777c2..a2d8ade2c2 100644 --- a/mods/d2k/rules/world.yaml +++ b/mods/d2k/rules/world.yaml @@ -243,3 +243,4 @@ EditorWorld: D2kEditorResourceLayer: EditorSelectionLayer: LoadWidgetAtGameStart: + EditorActionManager: diff --git a/mods/ra/rules/world.yaml b/mods/ra/rules/world.yaml index d80e49d61d..e6fd2bc601 100644 --- a/mods/ra/rules/world.yaml +++ b/mods/ra/rules/world.yaml @@ -284,3 +284,4 @@ EditorWorld: EditorResourceLayer: EditorSelectionLayer: LoadWidgetAtGameStart: + EditorActionManager: diff --git a/mods/ts/rules/world.yaml b/mods/ts/rules/world.yaml index 21107baf96..c5a4f4eac9 100644 --- a/mods/ts/rules/world.yaml +++ b/mods/ts/rules/world.yaml @@ -379,3 +379,4 @@ EditorWorld: EditorSelectionLayer: Palette: placefootprint LoadWidgetAtGameStart: + EditorActionManager: