Added Undo Redo to editor
This commit is contained in:
@@ -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<TooltipContainerWidget> 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<TooltipContainerWidget>(TooltipContainer));
|
||||
CurrentBrush = DefaultBrush = new EditorDefaultBrush(this, worldRenderer);
|
||||
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<EditorActorLayer>();
|
||||
editorActionManager = world.WorldActor.Trait<EditorActionManager>();
|
||||
|
||||
editor = widget.Parent.Get<EditorViewportControllerWidget>("MAP_EDITOR");
|
||||
actorEditPanel = editor.Get<BackgroundWidget>("ACTOR_EDIT_PANEL");
|
||||
|
||||
@@ -88,7 +96,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
initContainer.RemoveChildren();
|
||||
|
||||
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.IsVisible = () => actorIDStatus != ActorIDStatus.Normal;
|
||||
@@ -99,7 +108,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
if (logicArgs.TryGetValue("EditPanelPadding", out yaml))
|
||||
editPanelPadding = FieldLoader.GetValue<int>("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<DropDownButtonWidget>("OPTION");
|
||||
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) =>
|
||||
{
|
||||
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<LabelWidget>("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<float>(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<LabelWidget>("LABEL").GetText = () => ddo.Name;
|
||||
|
||||
var editorActionHandle = new EditorActorOptionActionHandle<string>(ddo.OnChange, ddo.GetValue(actor));
|
||||
editActorPreview.Add(editorActionHandle);
|
||||
|
||||
var dropdown = dropdownContainer.Get<DropDownButtonWidget>("OPTION");
|
||||
Func<KeyValuePair<string, string>, 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<LabelWidget>("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<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; }
|
||||
}
|
||||
}
|
||||
|
||||
71
OpenRA.Mods.Common/Widgets/Logic/Editor/HistoryLogLogic.cs
Normal file
71
OpenRA.Mods.Common/Widgets/Logic/Editor/HistoryLogLogic.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,21 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
if (reslayer != null)
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user