diff --git a/WDE.Conditions/Exporter/ConditionsSerializer.cs b/WDE.Conditions/Exporter/ConditionsSerializer.cs index 3426c2827..a6eec72c3 100644 --- a/WDE.Conditions/Exporter/ConditionsSerializer.cs +++ b/WDE.Conditions/Exporter/ConditionsSerializer.cs @@ -13,7 +13,7 @@ public static class ConditionsSerializer private static readonly string ConditionSql = @"({SourceTypeOrReferenceId}, {SourceGroup}, {SourceEntry}, {SourceId}, {ElseGroup}, {ConditionTypeOrReference}, {ConditionTarget}, {ConditionValue1}, {ConditionValue2}, {ConditionValue3}, {NegativeCondition}, {Comment})"; - public static object ToSqlObject(this IConditionLine line) + public static object ToSqlObject(this IConditionLine line, bool escapeComment = false) { return new { @@ -28,15 +28,13 @@ public static object ToSqlObject(this IConditionLine line) ConditionValue2 = line.ConditionValue2, ConditionValue3 = line.ConditionValue3, NegativeCondition = line.NegativeCondition, - Comment = line.Comment, + Comment = escapeComment ? line.Comment?.ToSqlEscapeString() : line.Comment, }; } public static string ToSqlString(this IConditionLine line) { - var obj = line.ToSqlObject(); - ((dynamic)obj).Comment = ((string)((dynamic)obj).Comment).ToSqlEscapeString(); - return Smart.Format(ConditionSql, obj); + return Smart.Format(ConditionSql, line.ToSqlObject(true)); } public static bool TryToConditionLine(this string str, out IConditionLine line) diff --git a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/GlobalVariableView.cs b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/GlobalVariableView.cs index 438861bc5..a937f42ff 100644 --- a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/GlobalVariableView.cs +++ b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/GlobalVariableView.cs @@ -9,21 +9,12 @@ namespace WDE.SmartScriptEditor.Avalonia.Editor.UserControls /// public class GlobalVariableView : SelectableTemplatedControl { - public static AvaloniaProperty DeselectAllRequestProperty = - AvaloniaProperty.Register(nameof(DeselectAllRequest)); - public static AvaloniaProperty DeselectAllButGlobalVariablesRequestProperty = AvaloniaProperty.Register(nameof(DeselectAllButGlobalVariablesRequest)); public static AvaloniaProperty EditGlobalVariableCommandProperty = AvaloniaProperty.Register(nameof(EditGlobalVariableCommand)); - public ICommand DeselectAllRequest - { - get => (ICommand) GetValue(DeselectAllRequestProperty); - set => SetValue(DeselectAllRequestProperty, value); - } - public ICommand DeselectAllButGlobalVariablesRequest { get => (ICommand) GetValue(DeselectAllButGlobalVariablesRequestProperty); @@ -36,22 +27,9 @@ public ICommand EditGlobalVariableCommand set => SetValue(EditGlobalVariableCommandProperty, value); } - protected override void OnPointerPressed(PointerPressedEventArgs e) + protected override void DeselectOthers() { - base.OnPointerPressed(e); - - if (e.ClickCount == 1) - { - DeselectAllButGlobalVariablesRequest?.Execute(null); - - if (!IsSelected) - { - if (!e.KeyModifiers.HasFlag(MultiselectGesture)) - DeselectAllRequest?.Execute(null); - IsSelected = true; - } - e.Handled = true; - } + DeselectAllButGlobalVariablesRequest?.Execute(null); } protected override void OnEdit() diff --git a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SelectableTemplatedControl.cs b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SelectableTemplatedControl.cs index d90f7c038..02bbd0d8e 100644 --- a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SelectableTemplatedControl.cs +++ b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SelectableTemplatedControl.cs @@ -1,3 +1,5 @@ +using System; +using System.Windows.Input; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; @@ -7,11 +9,19 @@ namespace WDE.SmartScriptEditor.Avalonia.Editor.UserControls { - public class SelectableTemplatedControl : TemplatedControl + public abstract class SelectableTemplatedControl : TemplatedControl { public static KeyModifiers MultiselectGesture { get; } = AvaloniaLocator.Current .GetService()?.CommandModifiers ?? KeyModifiers.Control; + public static readonly AvaloniaProperty DeselectAllRequestProperty = + AvaloniaProperty.Register(nameof(DeselectAllRequest)); + + public ICommand DeselectAllRequest + { + get => (ICommand) GetValue(DeselectAllRequestProperty); + set => SetValue(DeselectAllRequestProperty, value); + } public static readonly DirectProperty IsSelectedProperty = AvaloniaProperty.RegisterDirect( nameof(IsSelected), @@ -25,16 +35,43 @@ public bool IsSelected set => SetAndRaise(IsSelectedProperty, ref isSelected, value); } + private bool IsMultiSelect(KeyModifiers modifiers) + { + return modifiers.HasFlag(MultiselectGesture); + } + private ulong lastPressedTimestamp = 0; private int lastClickCount = 0; + private bool lastPressedWithControlOn = false; + private Point pressPosition; protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); lastPressedTimestamp = e.Timestamp; lastClickCount = e.ClickCount; + lastPressedWithControlOn = IsMultiSelect(e.KeyModifiers); + pressPosition = e.GetPosition(this); + + if (e.ClickCount == 1) + { + if (e.Source is FormattedTextBlock tb && tb.OverContext != null) + return; + + if (!IsSelected || !lastPressedWithControlOn) + DeselectOthers(); + + if (!lastPressedWithControlOn && !IsSelected) + { + DeselectAllRequest?.Execute(null); + IsSelected = true; + } + e.Handled = true; + } } + protected abstract void DeselectOthers(); + protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); @@ -45,6 +82,20 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) OnDirectEdit(tb.OverContext); e.Handled = true; } + else + { + if (lastPressedWithControlOn) + { + var vector = pressPosition - e.GetPosition(this); + var dist = Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y); + if (dist < 5) + { + DeselectOthers(); + IsSelected = !IsSelected; + e.Handled = true; + } + } + } } if (lastClickCount == 2 && (e.Timestamp - lastPressedTimestamp) <= 1000) { diff --git a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartActionView.cs b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartActionView.cs index 2d650910d..4c589baf3 100644 --- a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartActionView.cs +++ b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartActionView.cs @@ -12,9 +12,6 @@ namespace WDE.SmartScriptEditor.Avalonia.Editor.UserControls /// public class SmartActionView : SelectableTemplatedControl { - public static AvaloniaProperty DeselectAllRequestProperty = - AvaloniaProperty.Register(nameof(DeselectAllRequest)); - public static AvaloniaProperty DeselectAllButActionsRequestProperty = AvaloniaProperty.Register(nameof(DeselectAllButActionsRequest)); @@ -28,12 +25,6 @@ public class SmartActionView : SelectableTemplatedControl public static readonly DirectProperty IndentProperty = AvaloniaProperty.RegisterDirect("Indent", o => o.Indent, (o, v) => o.Indent = v); - public ICommand DeselectAllRequest - { - get => (ICommand) GetValue(DeselectAllRequestProperty); - set => SetValue(DeselectAllRequestProperty, value); - } - public ICommand DeselectAllButActionsRequest { get => (ICommand) GetValue(DeselectAllButActionsRequestProperty); @@ -57,28 +48,10 @@ public int Indent get { return indent; } set { SetAndRaise(IndentProperty, ref indent, value); } } - - protected override void OnPointerPressed(PointerPressedEventArgs e) + + protected override void DeselectOthers() { - base.OnPointerPressed(e); - - if (e.ClickCount == 1) - { - if (e.Source is FormattedTextBlock tb && tb.OverContext != null) - { - return; - } - - DeselectAllButActionsRequest?.Execute(null); - - if (!IsSelected) - { - if (!e.KeyModifiers.HasFlag(MultiselectGesture)) - DeselectAllRequest?.Execute(null); - IsSelected = true; - } - e.Handled = true; - } + DeselectAllButActionsRequest?.Execute(null); } protected override void OnDirectEdit(object context) diff --git a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartConditionView.cs b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartConditionView.cs index 2a33213b6..37071fc1a 100644 --- a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartConditionView.cs +++ b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartConditionView.cs @@ -10,9 +10,6 @@ namespace WDE.SmartScriptEditor.Avalonia.Editor.UserControls /// public class SmartConditionView : SelectableTemplatedControl { - public static readonly AvaloniaProperty DeselectAllRequestProperty = - AvaloniaProperty.Register(nameof(DeselectAllRequest)); - public static readonly AvaloniaProperty DeselectAllButConditionsRequestProperty = AvaloniaProperty.Register(nameof(DeselectAllButConditionsRequest)); @@ -22,11 +19,6 @@ public class SmartConditionView : SelectableTemplatedControl public static readonly AvaloniaProperty DirectEditParameterProperty = AvaloniaProperty.Register(nameof(DirectEditParameter)); - public ICommand DeselectAllRequest - { - get => (ICommand) GetValue(DeselectAllRequestProperty); - set => SetValue(DeselectAllRequestProperty, value); - } public ICommand DirectEditParameter { @@ -56,27 +48,9 @@ protected override void OnDirectEdit(object context) DirectEditParameter?.Execute(context); } - protected override void OnPointerPressed(PointerPressedEventArgs e) + protected override void DeselectOthers() { - base.OnPointerPressed(e); - - if (e.ClickCount == 1) - { - if (e.Source is FormattedTextBlock tb && tb.OverContext != null) - { - return; - } - - DeselectAllButConditionsRequest?.Execute(null); - - if (!IsSelected) - { - if (!e.KeyModifiers.HasFlag(MultiselectGesture)) - DeselectAllRequest?.Execute(null); - IsSelected = true; - } - e.Handled = true; - } + DeselectAllButConditionsRequest?.Execute(null); } } } \ No newline at end of file diff --git a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartEventView.cs b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartEventView.cs index 52dc7ba35..bc9fa1b85 100644 --- a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartEventView.cs +++ b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartEventView.cs @@ -17,12 +17,6 @@ public class SmartEventView : SelectableTemplatedControl o => o.EditEventCommand, (o, v) => o.EditEventCommand = v); - public static readonly DirectProperty DeselectAllRequestProperty = - AvaloniaProperty.RegisterDirect( - nameof(DeselectAllRequest), - o => o.DeselectAllRequest, - (o, v) => o.DeselectAllRequest = v); - public static readonly DirectProperty DeselectActionsOfDeselectedEventsRequestProperty = AvaloniaProperty.RegisterDirect( nameof(DeselectActionsOfDeselectedEventsRequest), @@ -42,14 +36,7 @@ public ICommand? EditEventCommand get => editEventCommand; set => SetAndRaise(EditEventCommandProperty, ref editEventCommand, value); } - - private ICommand? deselectAllRequest; - public ICommand? DeselectAllRequest - { - get => deselectAllRequest; - set => SetAndRaise(DeselectAllRequestProperty, ref deselectAllRequest, value); - } - + private ICommand? deselectActionsOfDeselectedEventsRequest; public ICommand? DeselectActionsOfDeselectedEventsRequest { @@ -74,28 +61,9 @@ protected override void OnDirectEdit(object context) DirectEditParameter?.Execute(context); } - protected override void OnPointerPressed(PointerPressedEventArgs e) + protected override void DeselectOthers() { - base.OnPointerPressed(e); - - if (e.ClickCount == 1) - { - if (e.Source is FormattedTextBlock tb && tb.OverContext != null) - { - return; - } - - DeselectActionsOfDeselectedEventsRequest?.Execute(null); - - if (!GetSelected(this)) - { - if (!e.KeyModifiers.HasFlag(MultiselectGesture)) - DeselectAllRequest?.Execute(null); - SetSelected(this, true); - } - - e.Handled = true; - } + DeselectActionsOfDeselectedEventsRequest?.Execute(null); } public static readonly AvaloniaProperty SelectedProperty = AvaloniaProperty.RegisterAttached("Selected"); diff --git a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartScriptPanelLayout.cs b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartScriptPanelLayout.cs index e42af2a4b..c84410288 100644 --- a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartScriptPanelLayout.cs +++ b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartScriptPanelLayout.cs @@ -7,6 +7,7 @@ using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Media; using WDE.Common.Managers; @@ -20,8 +21,8 @@ internal class SmartScriptPanelLayout : Panel { private readonly List<(float y, float height, int actionIndex, int eventIndex)> actionHeights = new(); private readonly List<(float y, float height, int conditionIndex, int eventIndex)> conditionHeights = new(); - - private readonly List eventHeights = new(); + private readonly List<(float y, float height, int eventIndex)> eventHeights = new(); + private readonly Dictionary actionToPresenter = new(); private readonly Dictionary eventToPresenter = new(); private readonly Dictionary conditionToPresenter = new(); @@ -39,10 +40,12 @@ internal class SmartScriptPanelLayout : Panel private bool draggingActions; private bool draggingEvents; private bool draggingConditions; + private bool isCopying; private Point mouseStartPosition; private float mouseY; + public (float y, float height, int eventIndex) OverIndexEvent { get; set; } private (float y, float height, int conditionIndex, int eventIndex) overIndexCondition; private (float y, float height, int actionIndex, int eventIndex) overIndexAction; @@ -253,6 +256,7 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) { base.OnPointerPressed(e); + UpdateIsCopying(e.KeyModifiers); if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && e.ClickCount == 1) { @@ -262,25 +266,59 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) } } + private void UpdateIsCopying(KeyModifiers key) + { + var systemWideControlModifier = AvaloniaLocator.Current + .GetService()?.CommandModifiers ?? KeyModifiers.Control; + isCopying = key.HasFlag(systemWideControlModifier); + } + protected override void OnPointerReleased(PointerReleasedEventArgs e) { + var systemWideControlModifier = AvaloniaLocator.Current + .GetService()?.CommandModifiers ?? KeyModifiers.Control; + base.OnPointerReleased(e); + UpdateIsCopying(e.KeyModifiers); StopDragging(); } - private void StopDragging() + private bool AnythingSelected() { - if (draggingEvents) - DropItems?.Execute(OverIndexEvent); - else if (draggingActions) + foreach (var e in Script.Events) { - DropActions?.Execute(new DropActionsConditionsArgs - {EventIndex = overIndexAction.eventIndex, ActionIndex = overIndexAction.actionIndex}); + if (e.IsSelected) + return true; } - else if (draggingConditions) + foreach (var e in Script.Events) { - DropConditions?.Execute(new DropActionsConditionsArgs - {EventIndex = overIndexCondition.eventIndex, ActionIndex = overIndexCondition.conditionIndex}); + foreach (var a in e.Actions) + if (a.IsSelected) + return true; + foreach (var a in e.Conditions) + if (a.IsSelected) + return true; + } + + return false; + } + + private void StopDragging() + { + if (AnythingSelected()) + { + if (draggingEvents) + DropItems?.Execute(new DropActionsConditionsArgs{EventIndex = OverIndexEvent.eventIndex, ActionIndex = 0, Copy = isCopying}); + else if (draggingActions) + { + DropActions?.Execute(new DropActionsConditionsArgs + {EventIndex = overIndexAction.eventIndex, ActionIndex = overIndexAction.actionIndex, Copy = isCopying}); + } + else if (draggingConditions) + { + DropConditions?.Execute(new DropActionsConditionsArgs + {EventIndex = overIndexCondition.eventIndex, ActionIndex = overIndexCondition.conditionIndex, Copy = isCopying}); + } } draggingEvents = false; @@ -295,17 +333,29 @@ private void StopDragging() public override void Render(DrawingContext dc) { base.Render(dc); - if (draggingActions) - { - double x = EventWidth(Bounds.Width); - float y = overIndexAction.y - overIndexAction.height / 2 - 1; - dc.DrawLine(new Pen(Brushes.Gray, 1), new Point(x, y), new Point(x + 200, y)); - } - else if (draggingConditions) + if (AnythingSelected()) { - float x = 1; - float y = overIndexCondition.y - overIndexCondition.height / 2 - 1; - dc.DrawLine(new Pen(Brushes.Gray, 1), new Point(x, y), new Point(x + ConditionWidth(Bounds.Width), y)); + if (draggingActions) + { + double x = EventWidth(Bounds.Width); + float y = overIndexAction.y - overIndexAction.height / 2 - 1; + dc.DrawLine(new Pen(Brushes.Gray, 1), new Point(x, y), new Point(x + 200, y)); + } + else if (draggingConditions) + { + float x = 1; + float y = overIndexCondition.y - overIndexCondition.height / 2 - 1; + dc.DrawLine(new Pen(Brushes.Gray, 1), new Point(x, y), new Point(x + ConditionWidth(Bounds.Width), y)); + } + else if (draggingEvents) + { + if (isCopying) + { + float x = 1; + float y = OverIndexEvent.y - OverIndexEvent.height / 2 - 1; + dc.DrawLine(new Pen(Brushes.Gray, 1), new Point(x, y), new Point(x + EventWidth(Bounds.Width), y)); + } + } } if (vvvvText == null) @@ -404,9 +454,9 @@ protected override void OnPointerMoved(PointerEventArgs e) { base.OnPointerMoved(e); + UpdateIsCopying(e.KeyModifiers); var state = e.GetCurrentPoint(this); - - + mouseY = (float) e.GetPosition(this).Y; if (!state.Properties.IsLeftButtonPressed) { @@ -444,11 +494,11 @@ protected override void OnPointerMoved(PointerEventArgs e) { var eventIndex = 0; var found = false; - foreach (float f in eventHeights) + foreach (var tuple in eventHeights) { - if (f > mouseY) + if (tuple.y > mouseY) { - OverIndexEvent = eventIndex; + OverIndexEvent = tuple; found = true; break; } @@ -456,11 +506,11 @@ protected override void OnPointerMoved(PointerEventArgs e) eventIndex++; } - if (!found) + if (!found && eventHeights.Count >= 1) { - OverIndexEvent = eventHeights.Count > 0 && mouseY > eventHeights[^1] - ? eventHeights.Count - 1 - : 0; + OverIndexEvent = eventHeights[^1];//eventHeights.Count > 0 && mouseY > eventHeights[^1].y + //? eventHeights[^1] + //: eventHeights[0]; } } else if (draggingActions) @@ -527,32 +577,35 @@ protected override Size ArrangeOverride(Size finalSize) addConditionPresenter?.Arrange(new Rect(-5, -5, 1, 1)); } + float lastHeight = 0; float selectedHeight = 0; + int eventIndex = 0; foreach (SmartEvent ev in Script.Events) { + eventIndex++; if (!eventToPresenter.TryGetValue(ev, out var eventPresenter)) continue; - float height = Math.Max(MeasureActions(eventPresenter), (float) eventPresenter.DesiredSize.Height + MeasureConditions(eventPresenter)); - eventHeights.Add(y + (height + EventSpacing) / 2); - y += height + EventSpacing; + lastHeight = Math.Max(MeasureActions(eventPresenter), (float) eventPresenter.DesiredSize.Height + MeasureConditions(eventPresenter)); + eventHeights.Add((y + (lastHeight + EventSpacing) / 2, lastHeight + EventSpacing, eventIndex - 1)); + y += lastHeight + EventSpacing; - if (!draggingEvents || !GetSelected(eventPresenter.Child)) + if (!draggingEvents || isCopying || !GetSelected(eventPresenter.Child)) continue; - selectedHeight += height + EventSpacing; + selectedHeight += lastHeight + EventSpacing; } - eventHeights.Add(y); + eventHeights.Add((y, 0, eventIndex)); y = (float)globalVariablesArrangement.Height; float start = 0; - if (OverIndexEvent == 0) + if (OverIndexEvent.eventIndex == 0) { start = y; y += selectedHeight; } - var eventIndex = 0; + eventIndex = 0; if (addActionViewModel != null) addActionViewModel.Event = null; if (addConditionViewModel != null) @@ -564,7 +617,7 @@ protected override Size ArrangeOverride(Size finalSize) continue; var height = (float)eventPresenter.DesiredSize.Height; - if (!draggingEvents || !GetSelected(eventPresenter.Child)) + if (!draggingEvents || isCopying || !GetSelected(eventPresenter.Child)) { float actionHeight = ArrangeActions(eventIndex, 0, finalSize, eventPresenter, y, height); float conditionsHeight = ArrangeConditions(eventIndex, (float)PaddingLeft, finalSize, eventPresenter, y, height); @@ -598,7 +651,7 @@ protected override Size ArrangeOverride(Size finalSize) } eventIndex++; - if (eventIndex == OverIndexEvent) + if (eventIndex == OverIndexEvent.eventIndex) { start = y; y += selectedHeight; @@ -611,7 +664,7 @@ protected override Size ArrangeOverride(Size finalSize) if (!eventToPresenter.TryGetValue(ev, out var eventPresenter)) continue; var height = (float) eventPresenter.DesiredSize.Height; - if (draggingEvents && GetSelected(eventPresenter.Child)) + if (draggingEvents && !isCopying && GetSelected(eventPresenter.Child)) { float conditionsHeight = ArrangeConditions(eventIndex, 20 + (float)PaddingLeft, finalSize, eventPresenter, start, height); height = Math.Max(height + conditionsHeight, ArrangeActions(eventIndex, 20, finalSize, eventPresenter, start, height)); @@ -735,8 +788,6 @@ private float ArrangeConditions(int eventIndex, // } // - public int OverIndexEvent { get; set; } - public SmartScript Script { get => (SmartScript) GetValue(ScriptProperty); diff --git a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartScriptView.axaml b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartScriptView.axaml index fcdd9e7a5..e59f1ae89 100644 --- a/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartScriptView.axaml +++ b/WDE.SmartScriptEditor.Avalonia/Editor/UserControls/SmartScriptView.axaml @@ -60,7 +60,7 @@ Selected="{Binding IsSelected, Mode=TwoWay}" userControls:SmartScriptPanelLayout.Selected="{Binding IsSelected, Mode=TwoWay}" DirectEditParameter="{Binding DataContext.DirectEditParameter, ElementName=Rooot}" - DeselectActionsOfDeselectedEventsRequest="{Binding DataContext.DeselectActions, ElementName=Rooot}" + DeselectActionsOfDeselectedEventsRequest="{Binding DataContext.DeselectAllButEvents, ElementName=Rooot}" DeselectAllRequest="{Binding DataContext.DeselectAll, ElementName=Rooot}" IsSelected="{Binding IsSelected, Mode=TwoWay}" EditEventCommand="{Binding DataContext.EditEvent, ElementName=Rooot}" @@ -70,7 +70,7 @@ WoW Database Editor has special "wait" action that can be used instead of timed action lists. Check it out! In the WoW Database Editor you can have many actions in a single event. You do not have to copy-paste events. You can name stored object variables to make writing script easier. To add named stored object, use right click on 'Add Event'. + You can hold control key while dragging event/action/condition to duplicate them. \ No newline at end of file diff --git a/WDE.SmartScriptEditor/Editor/DropActionsConditionsArgs.cs b/WDE.SmartScriptEditor/Editor/DropActionsConditionsArgs.cs index ea5971ddd..d1326b8a8 100644 --- a/WDE.SmartScriptEditor/Editor/DropActionsConditionsArgs.cs +++ b/WDE.SmartScriptEditor/Editor/DropActionsConditionsArgs.cs @@ -4,5 +4,7 @@ public class DropActionsConditionsArgs { public int ActionIndex; public int EventIndex; + public bool Copy; + public bool Move => !Copy; } } \ No newline at end of file diff --git a/WDE.SmartScriptEditor/Editor/ViewModels/SmartScriptEditorViewModel.cs b/WDE.SmartScriptEditor/Editor/ViewModels/SmartScriptEditorViewModel.cs index 3bd806ae9..5634933bd 100644 --- a/WDE.SmartScriptEditor/Editor/ViewModels/SmartScriptEditorViewModel.cs +++ b/WDE.SmartScriptEditor/Editor/ViewModels/SmartScriptEditorViewModel.cs @@ -144,8 +144,10 @@ public SmartScriptEditorViewModel(ISmartScriptSolutionItem item, smartEditorViewModel.ShowEditor(null, null); }); EditEvent = new DelegateCommand(EditEventCommand); - DeselectActions = new DelegateCommand(() => + DeselectAllButEvents = new DelegateCommand(() => { + foreach (var gv in script.GlobalVariables) + gv.IsSelected = false; foreach (SmartEvent e in Events) { if (!e.IsSelected) @@ -207,23 +209,29 @@ public SmartScriptEditorViewModel(ISmartScriptSolutionItem item, e.IsSelected = false; } }); - OnDropItems = new DelegateCommand(destIndex => + OnDropItems = new DelegateCommand(args => { - if (!destIndex.HasValue) + if (args == null) return; + + var destIndex = args.EventIndex; - using (script!.BulkEdit("Reorder events")) + using (script!.BulkEdit(args.Move ? "Reorder events" : "Copy events")) { var selected = new List(); - int d = destIndex.Value; + int d = destIndex; for (int i = Events.Count - 1; i >= 0; --i) { if (Events[i].IsSelected) { - if (i <= destIndex) - d--; selected.Add(Events[i]); - script.Events.RemoveAt(i); + Events[i].IsSelected = false; + if (args.Move) + { + if (i <= destIndex) + d--; + script.Events.RemoveAt(i); + } } } @@ -231,59 +239,89 @@ public SmartScriptEditorViewModel(ISmartScriptSolutionItem item, d = 0; selected.Reverse(); foreach (SmartEvent s in selected) - script.Events.Insert(d++, s); + { + var newEvent = args.Copy ? s.DeepCopy() : s; + newEvent.IsSelected = true; + script.Events.Insert(d++, newEvent); + } } }); OnDropActions = new DelegateCommand(data => { - using (script!.BulkEdit("Reorder actions")) + using (script!.BulkEdit(data.Move ? "Reorder actions" : "Copy actions")) { + if (data.EventIndex < 0 || data.EventIndex >= Events.Count) + { + Console.WriteLine("Fatal error! event index out of range"); + return; + } var selected = new List(); int d = data.ActionIndex; - for (var eventIndex = 0; eventIndex < Events.Count; eventIndex++) + for (var eventIndex = Events.Count - 1; eventIndex >= 0; eventIndex--) { SmartEvent e = Events[eventIndex]; for (int i = e.Actions.Count - 1; i >= 0; --i) { if (e.Actions[i].IsSelected) { - if (eventIndex == data.EventIndex && i < data.ActionIndex) - d--; selected.Add(e.Actions[i]); - e.Actions.RemoveAt(i); + e.Actions[i].IsSelected = false; + if (data.Move) + { + if (eventIndex == data.EventIndex && i < data.ActionIndex) + d--; + e.Actions.RemoveAt(i); + } } } } selected.Reverse(); foreach (SmartAction s in selected) - Events[data.EventIndex].Actions.Insert(d++, s); + { + SmartAction newAction = data.Copy ? s.Copy() : s; + newAction.IsSelected = true; + Events[data.EventIndex].Actions.Insert(d++, newAction); + } } }); OnDropConditions = new DelegateCommand(data => { - using (script!.BulkEdit("Reorder conditions")) + using (script!.BulkEdit(data.Move ? "Reorder conditions" : "Copy conditions")) { + if (data.EventIndex < 0 || data.EventIndex >= Events.Count) + { + Console.WriteLine("Fatal error! event index out of range"); + return; + } var selected = new List(); int d = data.ActionIndex; - for (var eventIndex = 0; eventIndex < Events.Count; eventIndex++) + for (var eventIndex = Events.Count - 1; eventIndex >= 0; eventIndex--) { SmartEvent e = Events[eventIndex]; for (int i = e.Conditions.Count - 1; i >= 0; --i) { if (e.Conditions[i].IsSelected) { - if (eventIndex == data.EventIndex && i < data.ActionIndex) - d--; selected.Add(e.Conditions[i]); - e.Conditions.RemoveAt(i); + e.Conditions[i].IsSelected = false; + if (data.Move) + { + if (eventIndex == data.EventIndex && i < data.ActionIndex) + d--; + e.Conditions.RemoveAt(i); + } } } } selected.Reverse(); foreach (SmartCondition s in selected) - Events[data.EventIndex].Conditions.Insert(d++, s); + { + var newCondition = data.Copy ? s.Copy() : s; + newCondition.IsSelected = true; + Events[data.EventIndex].Conditions.Insert(d++, newCondition); + } } }); @@ -500,7 +538,7 @@ public SmartScriptEditorViewModel(ISmartScriptSolutionItem item, EditConditionCommand(Events[FirstSelectedConditionIndex.eventIndex].Conditions[FirstSelectedConditionIndex.conditionIndex]); }); - CopyCommand = new DelegateCommand(() => + CopyCommand = new AsyncCommand(async () => { var selectedEvents = Events.Where(e => e.IsSelected).ToList(); if (selectedEvents.Count > 0) @@ -546,12 +584,12 @@ public SmartScriptEditorViewModel(ISmartScriptSolutionItem item, } } }); - CutCommand = new DelegateCommand(() => + CutCommand = new AsyncCommand(async () => { - CopyCommand.Execute(); + await CopyCommand.ExecuteAsync(); DeleteSelected.Execute(); }); - PasteCommand = new DelegateCommand(async () => + PasteCommand = new AsyncCommand(async () => { if (string.IsNullOrEmpty(await clipboard.GetText())) return; @@ -743,6 +781,7 @@ public SmartScriptEditorViewModel(ISmartScriptSolutionItem item, } } } + OnPaste?.Invoke(); }); Action selectionUpDown = (addToSelection, diff) => @@ -926,13 +965,13 @@ public SmartScriptEditorViewModel(ISmartScriptSolutionItem item, public DelegateCommand DeselectAllButGlobalVariables { get; set; } public DelegateCommand DeselectAllButConditions { get; set; } public DelegateCommand DeselectAllButActions { get; set; } - public DelegateCommand DeselectActions { get; set; } + public DelegateCommand DeselectAllButEvents { get; set; } public AsyncAutoCommand EditGlobalVariable { get; } public DelegateCommand DirectEditParameter { get; } public DelegateCommand OnDropConditions { get; set; } public DelegateCommand OnDropActions { get; set; } - public DelegateCommand OnDropItems { get; set; } + public DelegateCommand OnDropItems { get; set; } public AsyncAutoCommand AddAction { get; set; } public AsyncAutoCommand AddCondition { get; set; } @@ -948,9 +987,10 @@ public SmartScriptEditorViewModel(ISmartScriptSolutionItem item, public DelegateCommand UndoCommand { get; set; } public DelegateCommand RedoCommand { get; set; } - public DelegateCommand CopyCommand { get; set; } - public DelegateCommand CutCommand { get; set; } - public DelegateCommand PasteCommand { get; set; } + public AsyncCommand CopyCommand { get; set; } + public AsyncCommand CutCommand { get; set; } + public AsyncCommand PasteCommand { get; set; } + public event Action? OnPaste; public DelegateCommand SaveCommand { get; set; } public DelegateCommand DeleteSelected { get; set; } public DelegateCommand EditSelected { get; set; } @@ -1042,7 +1082,7 @@ private void SetSolutionItem(ISmartScriptSolutionItem item) updateInspections = true; }; - TeachingTips = AutoDispose(new SmartTeachingTips(teachingTipService, Script)); + TeachingTips = AutoDispose(new SmartTeachingTips(teachingTipService, this, Script)); Script.ScriptSelectedChanged += EventChildrenSelectionChanged; Together.Add(new NewActionViewModel()); diff --git a/WDE.SmartScriptEditor/Editor/ViewModels/SmartTeachingTips.cs b/WDE.SmartScriptEditor/Editor/ViewModels/SmartTeachingTips.cs index 6685eb195..02cdb5a06 100644 --- a/WDE.SmartScriptEditor/Editor/ViewModels/SmartTeachingTips.cs +++ b/WDE.SmartScriptEditor/Editor/ViewModels/SmartTeachingTips.cs @@ -10,24 +10,37 @@ public class SmartTeachingTips : BindableBase, System.IDisposable private const string TipWaitAction = "SmartScriptEditor.WaitActionTipOpened"; private const string TipMultipleActions = "SmartScriptEditor.MultipleActions"; private const string TipYouCanNameStoredTargets = "SmartScriptEditor.YouCanNameStoredTargets"; + private const string TipControlToCopy = "SmartScriptEditor.ControlToCopy"; private readonly ITeachingTipService teachingTipService; + private readonly SmartScriptEditorViewModel vm; private readonly SmartScript script; public bool MultipleActionsTipOpened { get; set; } public bool WaitActionTipOpened { get; set; } public bool YouCanNameStoredTargetsTipOpened { get; set; } + public bool ControlToCopyOpened { get; set; } - public SmartTeachingTips(ITeachingTipService teachingTipService, SmartScript script) + public SmartTeachingTips(ITeachingTipService teachingTipService, SmartScriptEditorViewModel vm, SmartScript script) { this.teachingTipService = teachingTipService; + this.vm = vm; this.script = script; } + private void OnPaste() + { + if (teachingTipService.ShowTip(TipControlToCopy)) + { + ControlToCopyOpened = true; + RaisePropertyChanged(nameof(ControlToCopyOpened)); + } + } + private void AllActionsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems == null) return; - + foreach (SmartAction item in e.NewItems) { // WaitActionTipOpened @@ -112,6 +125,7 @@ private bool AreEventsEqual(SmartEvent a, SmartEvent b) public void Dispose() { + vm.OnPaste -= OnPaste; script.AllActions.CollectionChanged -= AllActionsOnCollectionChanged; } @@ -119,14 +133,18 @@ public void Start() { // subscribe only if tips not shown yet if (AnyTipToShow()) + { + vm.OnPaste += OnPaste; script.AllActions.CollectionChanged += AllActionsOnCollectionChanged; + } } private bool AnyTipToShow() { return !teachingTipService.IsTipShown(TipMultipleActions) || !teachingTipService.IsTipShown(TipWaitAction) || - !teachingTipService.IsTipShown(TipYouCanNameStoredTargets); + !teachingTipService.IsTipShown(TipYouCanNameStoredTargets) || + !teachingTipService.IsTipShown(TipControlToCopy); } } } \ No newline at end of file diff --git a/WDE.SmartScriptEditor/Models/SmartEvent.cs b/WDE.SmartScriptEditor/Models/SmartEvent.cs index 60fcb8aee..04783215f 100644 --- a/WDE.SmartScriptEditor/Models/SmartEvent.cs +++ b/WDE.SmartScriptEditor/Models/SmartEvent.cs @@ -165,6 +165,19 @@ public bool Equals(SmartEvent other) return Chance.Value == other.Chance.Value; } + public SmartEvent DeepCopy() + { + var shallow = ShallowCopy(); + + foreach (var action in Actions) + shallow.AddAction(action.Copy()); + + foreach (var condition in Conditions) + shallow.Conditions.Add(condition.Copy()); + + return shallow; + } + public SmartEvent ShallowCopy() { SmartEvent se = new(Id);