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

@@ -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;
}
}
}

View File

@@ -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; }
}
}

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)
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()

View File

@@ -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)