From 7c7924c0bbc35e1164c9672faab50a80c7ba9655 Mon Sep 17 00:00:00 2001 From: Zach Day Date: Sat, 12 Oct 2024 18:41:55 -0400 Subject: [PATCH 01/67] Add PageUp & PageDown bindings --- ...lEditorViewModel.UserInteractionActions.cs | 56 ++++++++++++------- .../Views/FumenVisualEditorView.xaml | 2 +- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs index bc4d0d04..8cee96ab 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs @@ -71,12 +71,12 @@ private bool IsUserRequestHideEditorObject public Visibility EditorLockedVisibility => IsLocked - ? Visibility.Hidden : Visibility.Visible; + ? Visibility.Hidden : Visibility.Visible; public Visibility EditorObjectVisibility => IsLocked || // 编辑器被锁住 IsUserRequestHideEditorObject // 用户要求隐藏(比如按下Q) - ? Visibility.Hidden : Visibility.Visible; + ? Visibility.Hidden : Visibility.Visible; public bool IsDesignMode => EditorObjectVisibility == Visibility.Visible; public bool IsPreviewMode => !IsDesignMode; @@ -505,15 +505,15 @@ public void ExecuteActionWithRememberCurrentTime(IUndoableAction action) { var curTime = CurrentPlayTime; var rememberAction = LambdaUndoAction.Create(action.Name, () => - { - curTime = CurrentPlayTime; - action.Execute(); - }, - () => - { - action.Undo(); - ScrollTo(curTime); - }); + { + curTime = CurrentPlayTime; + action.Execute(); + }, + () => + { + action.Undo(); + ScrollTo(curTime); + }); UndoRedoManager.ExecuteAction(rememberAction); } @@ -650,12 +650,12 @@ public void KeyboardAction_DeleteSelectingObjects() if (group.Key == typeof(LaneCurvePathControlObject)) { foreach (var item in group.OfType().Select(x => - { - if (cacheCurveControlsMap.TryGetValue(x, out var val)) - return (x, val.refObj, val.idx); - else - return default; - }).Where(x => x.x is not null).GroupBy(x => x.refObj)) + { + if (cacheCurveControlsMap.TryGetValue(x, out var val)) + return (x, val.refObj, val.idx); + else + return default; + }).Where(x => x.x is not null).GroupBy(x => x.refObj)) { var refObj = item.Key; foreach ((var cp, _, var idx) in item.OrderBy(x => x.idx)) @@ -1475,6 +1475,22 @@ public void RegisterSelectableObject(OngekiObjectBase obj, Vector2 centerPos, Ve hits[obj] = new Rect(centerPos.X - size.X / 2, centerPos.Y - size.Y / 2, size.X, size.Y); } + public void ScrollPage(int page) + { + TGrid changeGrid; + + if (!IsDesignMode && TGridCalculator.ConvertYToTGrid_PreviewMode(ViewHeight, this).ToList() is [{ } single]) { + changeGrid = single; + } + else { + changeGrid = TGridCalculator.ConvertYToTGrid_DesignMode(ViewHeight, this); + } + + var change = new GridOffset((float)changeGrid.TotalUnit * page, 0); + + ScrollTo(GetCurrentTGrid() + new GridOffset(change.Unit, change.Grid)); + } + private void OnWheelScrollViewer(MouseWheelEventArgs arg) { if (IsDesignMode && Setting.JudgeLineAlignBeat) @@ -1547,8 +1563,8 @@ public void OnMouseWheel(ActionExecutionContext e) /* 0: 7 11 13 14 3: 3 6 9 12 - 2: 1 2 4 8 - 5: 5 10 + 2: 1 2 4 8 + 5: 5 10 */ private readonly static int[] xGridUnitUpJumpTable = new[] { 0, 1, 2, 3, 4, 5, 3, 7, 0, 3, 0, 0, 0, 0, 0, 0 }; private readonly static int[] xGridUnitDownJumpTable = new[] { 0, 0, -1, 0, -2, 0, -3, 0, -4, -3, -5, 0, -3, 0, 0, 0 }; @@ -1566,7 +1582,7 @@ private void OnWheelXGridUnit(MouseWheelEventArgs arg) /* 0: 11 13 3: 3 6 9 12 - 2: 1 2 4 8 + 2: 1 2 4 8 5: 5 10 15 7: 7 14 */ diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml index eac012d7..b0a2c42f 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml @@ -16,7 +16,7 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:valueconverters="clr-namespace:OngekiFumenEditor.UI.ValueConverters" xmlns:viewmodels="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels" - cal:Message.Attach="[Key OemTilde] = [Action KeyboardAction_FastPlaceDockableObjectToWallLeft]; [Key D4] = [Action KeyboardAction_FastPlaceDockableObjectToWallRight]; [Key D3] = [Action KeyboardAction_FastPlaceDockableObjectToRight]; [Key D2] = [Action KeyboardAction_FastPlaceDockableObjectToCenter]; [Key D1] = [Action KeyboardAction_FastPlaceDockableObjectToLeft]; [Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Key F] = [Action KeyboardAction_FastSwitchFlickDirection($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; " + cal:Message.Attach="[Key OemTilde] = [Action KeyboardAction_FastPlaceDockableObjectToWallLeft]; [Key D4] = [Action KeyboardAction_FastPlaceDockableObjectToWallRight]; [Key D3] = [Action KeyboardAction_FastPlaceDockableObjectToRight]; [Key D2] = [Action KeyboardAction_FastPlaceDockableObjectToCenter]; [Key D1] = [Action KeyboardAction_FastPlaceDockableObjectToLeft]; [Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Key F] = [Action KeyboardAction_FastSwitchFlickDirection($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" d:DataContext="{d:DesignInstance Type=viewmodels:FumenVisualEditorViewModel}" d:DesignHeight="450" d:DesignWidth="800" From 73242fc4a37542bc0ab93a1423c19f08b3f00bf5 Mon Sep 17 00:00:00 2001 From: Zach Day Date: Mon, 14 Oct 2024 15:41:19 -0400 Subject: [PATCH 02/67] Add ctrl+wheel to change vertical scale --- .../FumenVisualEditorViewModel.UserInteractionActions.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs index 8cee96ab..cb14c3c0 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs @@ -1556,6 +1556,8 @@ public void OnMouseWheel(ActionExecutionContext e) OnWheelBeatSplit(arg); else if (Keyboard.IsKeyDown(Key.LeftShift)) OnWheelXGridUnit(arg); + else if (Keyboard.IsKeyDown(Key.LeftCtrl)) + OnWheelVerticalScale(arg); else OnWheelScrollViewer(arg); } @@ -1599,6 +1601,11 @@ private void OnWheelBeatSplit(MouseWheelEventArgs arg) Setting.BeatSplit = newVal; } + private void OnWheelVerticalScale(MouseWheelEventArgs arg) + { + Editor.Setting.VerticalDisplayScale = Math.Clamp(Editor.Setting.VerticalDisplayScale + Math.Sign(arg.Delta) * 0.3, 0.1, 3); + } + private bool isDraggingPlayerLocation = false; private double draggingPlayerLocationCurrentX = 0; From c0c076e857e967d7c80e6eebe7b608a22e24f5a1 Mon Sep 17 00:00:00 2001 From: Zach Day Date: Wed, 16 Oct 2024 17:45:58 -0400 Subject: [PATCH 03/67] Add keybinds for creating lanes in brush mode --- .../AddObject/AddObjectCommandDefinition.cs | 53 +++++++++ .../AddObject/AddObjectCommandHandler.cs | 49 ++++++++ .../FumenVisualEditorViewModel.Brush.cs | 27 +++++ ...lEditorViewModel.UserInteractionActions.cs | 74 ++++++++++++ .../Views/FumenVisualEditorView.xaml | 107 ++++++++++++++---- 5 files changed, 291 insertions(+), 19 deletions(-) create mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs create mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs create mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs new file mode 100644 index 00000000..0381c2e0 --- /dev/null +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs @@ -0,0 +1,53 @@ +using Gemini.Framework.Commands; +using OngekiFumenEditor.Base; +using OngekiFumenEditor.Base.OngekiObjects.Lane; +using OngekiFumenEditor.Base.OngekiObjects.Wall; +using OngekiFumenEditor.Utils; + +namespace OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject; + +[CommandDefinition] +public abstract class AddObjectCommandDefinition : CommandDefinition +{ + public abstract OngekiMovableObjectBase CreateOngekiObject(); + public abstract string LaneName { get; } + + public override string Name => $"AddObject_{LaneName}"; + public override string Text => $"Add {LaneName}"; + public override string ToolTip => $"Add a new {LaneName} to the fumen and select it"; +} + +[CommandDefinition] +public class AddObjectLaneLeftCommandDefinition : AddObjectCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new LaneLeftStart(); + public override string LaneName => "LaneLeft"; +} + +[CommandDefinition] +public class AddObjectLaneCenterCommandDefinition : AddObjectCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new LaneCenterStart(); + public override string LaneName => "LaneCenter"; +} + +[CommandDefinition] +public class AddObjectLaneRightCommandDefinition : AddObjectCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new LaneRightStart(); + public override string LaneName => "LaneRight"; +} + +[CommandDefinition] +public class AddObjectWallRightCommandDefinition : AddObjectCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new WallRightStart(); + public override string LaneName => "WallRight"; +} + +[CommandDefinition] +public class AddObjectWallLeftCommandDefinition : AddObjectCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new WallLeftStart(); + public override string LaneName => "WallLeft"; +} diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs new file mode 100644 index 00000000..0f54bb71 --- /dev/null +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using Caliburn.Micro; +using Gemini.Framework.Commands; +using OngekiFumenEditor.Modules.FumenVisualEditor.Kernel; +using OngekiFumenEditor.Modules.FumenVisualEditor.Views; +using OngekiFumenEditor.Utils; + +namespace OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject; + +[CommandHandler] +public class AddObjectCommandHandler : CommandHandlerBase where D : AddObjectCommandDefinition +{ + public override void Update(Command command) + { + base.Update(command); + command.Enabled = true; + } + + public override Task Run(Command command) + { + var cmd = (AddObjectCommandDefinition)command.CommandDefinition; + var ongekiObj = cmd.CreateOngekiObject(); + + var editor = IoC.Get()?.CurrentActivatedEditor; + editor!.MoveObjectTo(ongekiObj, editor.CurrentCursorPosition!.Value); + editor.Fumen.AddObject(ongekiObj); + editor.InteractiveManager.GetInteractive(ongekiObj).OnMoveCanvas(ongekiObj, editor.CurrentCursorPosition.Value, editor); + editor.NotifyObjectClicked(ongekiObj); + + return Task.CompletedTask; + } +} + +[CommandHandler] +public class AddObjectLaneLeftCommandHandler : AddObjectCommandHandler +{ } + +[CommandHandler] +public class AddObjectLaneCenterCommandHandler : AddObjectCommandHandler +{ } +[CommandHandler] +public class AddObjectLaneRightCommandHandler : AddObjectCommandHandler +{ } +[CommandHandler] +public class AddObjectWallLeftCommandHandler : AddObjectCommandHandler +{ } +[CommandHandler] +public class AddObjectWallRightCommandHandler : AddObjectCommandHandler +{ } diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs new file mode 100644 index 00000000..73945931 --- /dev/null +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs @@ -0,0 +1,27 @@ +using System.Windows.Input; +using Caliburn.Micro; +using Gemini.Framework; +using Gemini.Framework.Commands; +using OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject; +using OngekiFumenEditor.Utils; + +namespace OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels; + +public partial class FumenVisualEditorViewModel : PersistedDocument +{ + private void RunCommand(CommandDefinition def) + { + new TargetableCommand(new(def)).Execute(null); + } + + public void Brush_AddNewLaneLeft() + => RunCommand(new AddObjectLaneLeftCommandDefinition()); + public void Brush_AddNewLaneCenter() + => RunCommand(new AddObjectLaneCenterCommandDefinition()); + public void Brush_AddNewLaneRight() + => RunCommand(new AddObjectLaneRightCommandDefinition()); + public void Brush_AddNewWallLeft() + => RunCommand(new AddObjectWallLeftCommandDefinition()); + public void Brush_AddNewWallRight() + => RunCommand(new AddObjectWallRightCommandDefinition()); +} \ No newline at end of file diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs index cb14c3c0..a855a56d 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs @@ -32,9 +32,11 @@ using System.Windows; using System.Windows.Documents; using System.Windows.Input; +using OngekiFumenEditor.Base.EditorObjects.Svg; using OngekiFumenEditor.Base.OngekiObjects.Lane; using OngekiFumenEditor.Base.OngekiObjects.Lane.Base; using OngekiFumenEditor.Base.OngekiObjects.Wall; +using OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels.OngekiObjects; namespace OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels { @@ -120,6 +122,26 @@ public Rect SelectionRect public bool IsRangeSelecting => SelectionVisibility == Visibility.Visible; public bool IsPreventMutualExclusionSelecting { get; set; } + public void ClearSelection() + { + foreach (var obj in SelectObjects) { + obj.IsSelected = false; + } + } + + public void AddToSelection(OngekiMovableObjectBase obj) + { + obj.IsSelected = true; + NotifyObjectClicked(obj); + Log.LogInfo(obj.IsSelected.ToString()); + } + + public void ReplaceSelection(OngekiMovableObjectBase obj) + { + ClearSelection(); + AddToSelection(obj); + } + #endregion public IEnumerable SelectObjects => Fumen.GetAllDisplayableObjects().OfType().Where(x => x.IsSelected).Distinct(); @@ -1338,6 +1360,58 @@ private void TryApplyBrushObject(Point p) UndoRedoManager.ExecuteAction(LambdaUndoAction.Create(Resources.AddObjectsByBrush, redo, undo)); } + + #region Quick Add Actions + + public void KeyboardAction_AddNewWallLeft(bool clearSelection = true) + => TryCreateObjectAtMouse(new WallLeftStart(), clearSelection); + public void KeyboardAction_AddNewWallRight(bool clearSelection = true) + => TryCreateObjectAtMouse(new WallRightStart(), clearSelection); + public void KeyboardAction_AddNewLaneLeft(bool clearSelection = true) + => TryCreateObjectAtMouse(new LaneLeftStart(), clearSelection); + public void KeyboardAction_AddNewLaneCenter(bool clearSelection = true) + => TryCreateObjectAtMouse(new LaneCenterStart(), clearSelection); + public void KeyboardAction_AddNewLaneRight(bool clearSelection = true) + => TryCreateObjectAtMouse(new LaneRightStart(), clearSelection); + public void KeyboardAction_AddNewLaneColorful(bool clearSelection = true) + => TryCreateObjectAtMouse(new ColorfulLaneStart(), clearSelection); + + public void KeyboardAction_AddNewFlick(bool switchDirection = false, bool clearSelection = false) + => TryCreateObjectAtMouse(new Flick() { Direction = switchDirection ? Flick.FlickDirection.Right : Flick.FlickDirection.Left}, clearSelection); + + public void KeyboardAction_AddNewBlock(bool switchDirection = false, bool clearSelection = false) + => TryCreateObjectAtMouse( + new LaneBlockArea() + { + Direction = switchDirection ? LaneBlockArea.BlockDirection.Right : LaneBlockArea.BlockDirection.Left + }, clearSelection); + + public void KeyboardAction_AddNewTap(bool clearSelection = false) + { + TryCreateObjectAtMouse(new Tap(), clearSelection, false); + } + + #endregion + + private void TryCreateObjectAtMouse(OngekiObjectBase obj, bool clearSelection, bool autoSelectObj = true) + { + if (!CurrentCursorPosition.HasValue) { + return; + } + + MoveObjectTo(obj, CurrentCursorPosition.Value); + Fumen.AddObject(obj); + InteractiveManager.GetInteractive(obj).OnMoveCanvas(obj, CurrentCursorPosition.Value, this); + + if (clearSelection) { + SelectObjects.ForEach(o => o.IsSelected = false); + } + + if (autoSelectObj) { + ((ISelectableObject)obj).IsSelected = true; + NotifyObjectClicked(obj); + } + } #region Object Click&Selection diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml index b0a2c42f..3f3b44fe 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml @@ -16,7 +16,7 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:valueconverters="clr-namespace:OngekiFumenEditor.UI.ValueConverters" xmlns:viewmodels="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels" - cal:Message.Attach="[Key OemTilde] = [Action KeyboardAction_FastPlaceDockableObjectToWallLeft]; [Key D4] = [Action KeyboardAction_FastPlaceDockableObjectToWallRight]; [Key D3] = [Action KeyboardAction_FastPlaceDockableObjectToRight]; [Key D2] = [Action KeyboardAction_FastPlaceDockableObjectToCenter]; [Key D1] = [Action KeyboardAction_FastPlaceDockableObjectToLeft]; [Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Key F] = [Action KeyboardAction_FastSwitchFlickDirection($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" + cal:Message.Attach="[Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Key F] = [Action KeyboardAction_FastSwitchFlickDirection($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" d:DataContext="{d:DesignInstance Type=viewmodels:FumenVisualEditorViewModel}" d:DesignHeight="450" d:DesignWidth="800" @@ -38,6 +38,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -47,34 +93,55 @@ + - - + + - - + + - - + + - + - - - - + + + + - - + + - - - + + + @@ -84,11 +151,13 @@ IsChecked="{Binding IsDisplayFPS}" /> + + - + \ No newline at end of file From 13a1349b9d3f19a47bc3c64abf1456eef6d47589 Mon Sep 17 00:00:00 2001 From: Zach Day Date: Wed, 16 Oct 2024 19:56:03 -0400 Subject: [PATCH 04/67] Can press keys in brush mode to create most game objects - I'm not sure how I feel about this. There's a lot of friction involving entering brush mode and leaving it to gain access to the left mouse button. This feels especially bad when we have nothing in the clipboard and left mouse button doesn't do anything at all. - Solution: Brush Mode should contain a field that tracks a creation command. The user can press the keys we have set up now to change that field. - Should probably turn Brush Mode into a behavior. - For later consideration, we could have a "diret insertion" mode that replicates the behavior seen as of this commit. --- .../Behaviors/CommandActionMessage.cs | 28 +++++++ .../AddObject/AddObjectCommandDefinition.cs | 80 +++++++++++++++---- .../AddObject/AddObjectCommandHandler.cs | 56 ++++++++----- .../FumenVisualEditorViewModel.Brush.cs | 10 +-- ...lEditorViewModel.UserInteractionActions.cs | 2 +- .../Views/FumenVisualEditorView.xaml | 58 ++++++++++---- 6 files changed, 175 insertions(+), 59 deletions(-) create mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/CommandActionMessage.cs diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/CommandActionMessage.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/CommandActionMessage.cs new file mode 100644 index 00000000..ed200179 --- /dev/null +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/CommandActionMessage.cs @@ -0,0 +1,28 @@ +using System; +using System.Windows; +using System.Windows.Input; +using Caliburn.Micro; +using Gemini.Framework.Commands; +using Microsoft.Xaml.Behaviors; + +namespace OngekiFumenEditor.Modules.FumenVisualEditor.Base; + +public class CommandActionMessage : TriggerAction +{ + public Type CommandDefinition + { + get => (Type)GetValue(CommandDefinitionProperty); + set => SetValue(CommandDefinitionProperty, value); + } + + protected override void Invoke(object parameter) + { + var commandService = IoC.Get(); + var definition = commandService.GetCommandDefinition(CommandDefinition); + var command = commandService.GetTargetableCommand(commandService.GetCommand(definition)); + if (command.CanExecute(parameter)) + command.Execute(parameter); + } + + public static readonly DependencyProperty CommandDefinitionProperty = DependencyProperty.Register("CommandDefinition", typeof(Type), typeof(CommandActionMessage), null); +} \ No newline at end of file diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs index 0381c2e0..26baabd9 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs @@ -1,53 +1,103 @@ using Gemini.Framework.Commands; using OngekiFumenEditor.Base; +using OngekiFumenEditor.Base.OngekiObjects; using OngekiFumenEditor.Base.OngekiObjects.Lane; using OngekiFumenEditor.Base.OngekiObjects.Wall; using OngekiFumenEditor.Utils; namespace OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject; -[CommandDefinition] public abstract class AddObjectCommandDefinition : CommandDefinition { public abstract OngekiMovableObjectBase CreateOngekiObject(); - public abstract string LaneName { get; } + public abstract string ObjectName { get; } + public virtual bool AddToSelection { get; } = false; + + public override string Name => $"AddObject_{ObjectName}"; + public override string Text => $"Add {ObjectName}"; + public override string ToolTip => + AddToSelection ? $"Add a new {ObjectName} to the fumen and select it" : $"Add a new {ObjectName} to the fumen"; +} - public override string Name => $"AddObject_{LaneName}"; - public override string Text => $"Add {LaneName}"; - public override string ToolTip => $"Add a new {LaneName} to the fumen and select it"; +[CommandDefinition] +public abstract class AddLaneCommandDefinition : AddObjectCommandDefinition +{ + public override bool AddToSelection { get; } = true; } [CommandDefinition] -public class AddObjectLaneLeftCommandDefinition : AddObjectCommandDefinition +public class AddLaneLeftCommandDefinition : AddLaneCommandDefinition { public override OngekiMovableObjectBase CreateOngekiObject() => new LaneLeftStart(); - public override string LaneName => "LaneLeft"; + public override string ObjectName => "LaneLeft"; } [CommandDefinition] -public class AddObjectLaneCenterCommandDefinition : AddObjectCommandDefinition +public class AddLaneCenterCommandDefinition : AddLaneCommandDefinition { public override OngekiMovableObjectBase CreateOngekiObject() => new LaneCenterStart(); - public override string LaneName => "LaneCenter"; + public override string ObjectName => "LaneCenter"; } [CommandDefinition] -public class AddObjectLaneRightCommandDefinition : AddObjectCommandDefinition +public class AddLaneRightCommandDefinition : AddLaneCommandDefinition { public override OngekiMovableObjectBase CreateOngekiObject() => new LaneRightStart(); - public override string LaneName => "LaneRight"; + public override string ObjectName => "LaneRight"; } [CommandDefinition] -public class AddObjectWallRightCommandDefinition : AddObjectCommandDefinition +public class AddWallRightCommandDefinition : AddLaneCommandDefinition { public override OngekiMovableObjectBase CreateOngekiObject() => new WallRightStart(); - public override string LaneName => "WallRight"; + public override string ObjectName => "WallRight"; } [CommandDefinition] -public class AddObjectWallLeftCommandDefinition : AddObjectCommandDefinition +public class AddWallLeftCommandDefinition : AddLaneCommandDefinition { public override OngekiMovableObjectBase CreateOngekiObject() => new WallLeftStart(); - public override string LaneName => "WallLeft"; + public override string ObjectName => "WallLeft"; +} + +[CommandDefinition] +public class AddLaneColorfulCommandDefinition : AddLaneCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new ColorfulLaneStart(); + public override string ObjectName => "LaneColorful"; +} + +[CommandDefinition] +public class AddTapCommandDefinition : AddObjectCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new Tap(); + public override string ObjectName => "Tap"; +} + +[CommandDefinition] +public class AddHoldCommandDefinition : AddObjectCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new Hold(); + public override string ObjectName => "Hold"; + public override bool AddToSelection => true; +} + +[CommandDefinition] +public class AddFlickCommandDefinition : AddObjectCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new Flick(); + public override string ObjectName => "Flick"; +} + +[CommandDefinition] +public class AddFlickReversedCommandDefinition : AddFlickCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new Flick() { Direction = Flick.FlickDirection.Right }; +} + +[CommandDefinition] +public class AddLaneBlockCommandDefinition : AddObjectCommandDefinition +{ + public override OngekiMovableObjectBase CreateOngekiObject() => new Flick(); + public override string ObjectName => "LaneBlock"; } diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs index 0f54bb71..9eb9aaa8 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs @@ -1,6 +1,10 @@ +using System; using System.Threading.Tasks; using Caliburn.Micro; using Gemini.Framework.Commands; +using Gemini.Modules.UndoRedo; +using OngekiFumenEditor.Base; +using OngekiFumenEditor.Modules.FumenVisualEditor.Base; using OngekiFumenEditor.Modules.FumenVisualEditor.Kernel; using OngekiFumenEditor.Modules.FumenVisualEditor.Views; using OngekiFumenEditor.Utils; @@ -10,6 +14,8 @@ namespace OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject; [CommandHandler] public class AddObjectCommandHandler : CommandHandlerBase where D : AddObjectCommandDefinition { + private OngekiMovableObjectBase OngekiObject = null; + public override void Update(Command command) { base.Update(command); @@ -19,31 +25,37 @@ public override void Update(Command command) public override Task Run(Command command) { var cmd = (AddObjectCommandDefinition)command.CommandDefinition; - var ongekiObj = cmd.CreateOngekiObject(); - var editor = IoC.Get()?.CurrentActivatedEditor; - editor!.MoveObjectTo(ongekiObj, editor.CurrentCursorPosition!.Value); - editor.Fumen.AddObject(ongekiObj); - editor.InteractiveManager.GetInteractive(ongekiObj).OnMoveCanvas(ongekiObj, editor.CurrentCursorPosition.Value, editor); - editor.NotifyObjectClicked(ongekiObj); + editor?.UndoRedoManager.ExecuteAction(new LambdaUndoAction(command.CommandDefinition.Name, Redo, Undo)); + return Task.CompletedTask; + + void Redo() + { + OngekiObject = cmd.CreateOngekiObject(); + editor!.MoveObjectTo(OngekiObject, editor.CurrentCursorPosition!.Value); + editor.Fumen.AddObject(OngekiObject); + editor.InteractiveManager.GetInteractive(OngekiObject).OnMoveCanvas(OngekiObject, editor.CurrentCursorPosition.Value, editor); + if (cmd.AddToSelection) + editor.NotifyObjectClicked(OngekiObject); + } + + void Undo() + { + editor!.RemoveObject(OngekiObject); + OngekiObject = null; + } } } -[CommandHandler] -public class AddObjectLaneLeftCommandHandler : AddObjectCommandHandler -{ } - -[CommandHandler] -public class AddObjectLaneCenterCommandHandler : AddObjectCommandHandler -{ } -[CommandHandler] -public class AddObjectLaneRightCommandHandler : AddObjectCommandHandler -{ } -[CommandHandler] -public class AddObjectWallLeftCommandHandler : AddObjectCommandHandler -{ } -[CommandHandler] -public class AddObjectWallRightCommandHandler : AddObjectCommandHandler -{ } +[CommandHandler] public class AddObjectLaneLeftCommandHandler : AddObjectCommandHandler { } +[CommandHandler] public class AddObjectLaneCenterCommandHandler : AddObjectCommandHandler { } +[CommandHandler] public class AddObjectLaneRightCommandHandler : AddObjectCommandHandler { } +[CommandHandler] public class AddObjectWallLeftCommandHandler : AddObjectCommandHandler { } +[CommandHandler] public class AddObjectWallRightCommandHandler : AddObjectCommandHandler { } +[CommandHandler] public class AddObjectLaneColorfulCommandHandler : AddObjectCommandHandler { } +[CommandHandler] public class AddObjectTapCommandHandler : AddObjectCommandHandler { } +[CommandHandler] public class AddObjectHoldCommandHandler : AddObjectCommandHandler { } +[CommandHandler] public class AddObjectFlickCommandHandler : AddObjectCommandHandler { } +[CommandHandler] public class AddObjectFlickReversedCommandHandler : AddObjectCommandHandler { } diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs index 73945931..4282106b 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs @@ -15,13 +15,13 @@ private void RunCommand(CommandDefinition def) } public void Brush_AddNewLaneLeft() - => RunCommand(new AddObjectLaneLeftCommandDefinition()); + => RunCommand(new AddLaneLeftCommandDefinition()); public void Brush_AddNewLaneCenter() - => RunCommand(new AddObjectLaneCenterCommandDefinition()); + => RunCommand(new AddLaneCenterCommandDefinition()); public void Brush_AddNewLaneRight() - => RunCommand(new AddObjectLaneRightCommandDefinition()); + => RunCommand(new AddLaneRightCommandDefinition()); public void Brush_AddNewWallLeft() - => RunCommand(new AddObjectWallLeftCommandDefinition()); + => RunCommand(new AddWallLeftCommandDefinition()); public void Brush_AddNewWallRight() - => RunCommand(new AddObjectWallRightCommandDefinition()); + => RunCommand(new AddWallRightCommandDefinition()); } \ No newline at end of file diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs index a855a56d..7c6470d2 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs @@ -775,7 +775,7 @@ public void KeyboardAction_FastSetObjectIsCritical(ActionExecutionContext e) })); } - public void KeyboardAction_FastSwitchFlickDirection(ActionExecutionContext e) + public void KeyboardAction_FastSwitchFlickDirection() { var selectedFlicks = SelectObjects.OfType().ToList(); diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml index 3f3b44fe..ca2ab534 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml @@ -16,7 +16,10 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:valueconverters="clr-namespace:OngekiFumenEditor.UI.ValueConverters" xmlns:viewmodels="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels" - cal:Message.Attach="[Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Key F] = [Action KeyboardAction_FastSwitchFlickDirection($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" + xmlns:base="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Base" + xmlns:addObject="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject" + xmlns:brushModeSwitch="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Commands.BrushModeSwitch" + cal:Message.Attach="[Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" d:DataContext="{d:DesignInstance Type=viewmodels:FumenVisualEditorViewModel}" d:DesignHeight="450" d:DesignWidth="800" @@ -41,47 +44,70 @@ - + - + - + - + - + + + + + - + - - + - - + - - + - - + + + + - + + + + + + + + + + + + + + + + + + + + From 6075af2578db3ac84c16bb83ae4947d73932f887 Mon Sep 17 00:00:00 2001 From: Zach Day Date: Sat, 19 Oct 2024 22:49:54 -0400 Subject: [PATCH 05/67] Super brush mode --- OngekiFumenEditor/App.xaml | 1 + .../Behaviors/BrushMode/BrushModeBehavior.cs | 304 ++++++++++++++++++ .../BrushMode/BrushModeInputObject.cs | 125 +++++++ .../Behaviors/CommandActionMessage.cs | 28 -- .../AddObject/AddObjectCommandDefinition.cs | 103 ------ .../AddObject/AddObjectCommandHandler.cs | 61 ---- .../BrushModeSwitchCommandHandler.cs | 34 +- .../Editors/DrawSelectingRangeHelper.cs | 19 +- .../FumenVisualEditorViewModel.Brush.cs | 24 +- ...lEditorViewModel.UserInteractionActions.cs | 60 ++-- .../Views/BrushModeOverlayView.xaml | 46 +++ .../Views/BrushModeOverlayView.xaml.cs | 11 + .../Views/FumenVisualEditorView.xaml | 16 +- .../NotNullToBooleanConverter.cs | 18 ++ .../NullToCollapsedConverter.cs | 20 ++ .../UI/ValueConverters/NullToZeroConverter.cs | 21 ++ .../Utils/LambdaTriggerAction.cs | 21 ++ .../Utils/Vector2ExtensionMethod.cs | 3 + 18 files changed, 660 insertions(+), 255 deletions(-) create mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/BrushMode/BrushModeBehavior.cs create mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/BrushMode/BrushModeInputObject.cs delete mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/CommandActionMessage.cs delete mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs delete mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs create mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Views/BrushModeOverlayView.xaml create mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/Views/BrushModeOverlayView.xaml.cs create mode 100644 OngekiFumenEditor/UI/ValueConverters/NotNullToBooleanConverter.cs create mode 100644 OngekiFumenEditor/UI/ValueConverters/NullToCollapsedConverter.cs create mode 100644 OngekiFumenEditor/UI/ValueConverters/NullToZeroConverter.cs create mode 100644 OngekiFumenEditor/Utils/LambdaTriggerAction.cs diff --git a/OngekiFumenEditor/App.xaml b/OngekiFumenEditor/App.xaml index 3cee5a38..cc144950 100644 --- a/OngekiFumenEditor/App.xaml +++ b/OngekiFumenEditor/App.xaml @@ -25,6 +25,7 @@ + diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/BrushMode/BrushModeBehavior.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/BrushMode/BrushModeBehavior.cs new file mode 100644 index 00000000..55477182 --- /dev/null +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/BrushMode/BrushModeBehavior.cs @@ -0,0 +1,304 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Data; +using System.Windows.Input; +using Caliburn.Micro; +using Microsoft.Xaml.Behaviors; +using Microsoft.Xaml.Behaviors.Core; +using OngekiFumenEditor.Base; +using OngekiFumenEditor.Modules.FumenVisualEditor.Base; +using OngekiFumenEditor.Modules.FumenVisualEditor.Kernel; +using OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels; +using OngekiFumenEditor.Modules.FumenVisualEditor.Views; +using OngekiFumenEditor.UI.KeyBinding.Input; +using OngekiFumenEditor.Utils; +using EventTrigger = Microsoft.Xaml.Behaviors.EventTrigger; +using TriggerAction = Microsoft.Xaml.Behaviors.TriggerAction; +using TriggerBase = Microsoft.Xaml.Behaviors.TriggerBase; + +namespace OngekiFumenEditor.Modules.FumenVisualEditor.Behaviors.BrushMode; + +public class BrushModeBehavior : Behavior +{ + private readonly ImmutableDictionary CommandDefinitions; + private readonly ImmutableDictionary ClickTriggers; + + public BrushModeInputObject CurrentInputObject + { + get => (BrushModeInputObject)GetValue(CurrentInputObjectProperty); + set => SetValue(CurrentInputObjectProperty, value); + } + + private readonly List OldTriggers = new(); + private readonly List NewTriggers = new(); + private readonly IFumenEditorClipboard Clipboard; + + public BrushModeBehavior() + { + CommandDefinitions = new Dictionary + { + [Key.OemTilde] = new BrushModeInputWallLeft(), + [Key.D1] = new BrushModeInputLaneLeft(), + [Key.D2] = new BrushModeInputLaneCenter(), + [Key.D3] = new BrushModeInputLaneRight(), + [Key.D4] = new BrushModeInputWallRight(), + [Key.D5] = new BrushModeInputLaneColorful(), + [Key.T] = new BrushModeInputTap(), + [Key.D] = new BrushModeInputHold(), + [Key.E] = new BrushModeInputNormalBell(), + [Key.F] = new BrushModeInputFlick(), + [Key.Z] = new BrushModeInputLaneBlock(), + [Key.C] = null + }.ToImmutableDictionary(); + + ClickTriggers = new Dictionary + { + ["PreviewMouseDown"] = new LambdaTriggerAction(o => { MouseDown((MouseButtonEventArgs)o); }), + ["PreviewMouseUp"] = new LambdaTriggerAction(o => { MouseUp((MouseButtonEventArgs)o); }), + ["MouseMove"] = new LambdaTriggerAction(o => { MouseMove((MouseEventArgs)o);}) + }.ToImmutableDictionary(); + + Clipboard = IoC.Get(); + } + + protected override void OnAttached() + { + var triggerCollection = Interaction.GetTriggers(AssociatedObject); + + // Create brush key triggers on the FumenVisualEditorView. + // Temporarily delete existing ones that clash with brush keys. + foreach (var (key, obj) in CommandDefinitions) { + var existingTriggers = triggerCollection.Where(t => t is KeyTrigger tr && tr.Key == key); + OldTriggers.AddRange(existingTriggers); + OldTriggers.ForEach(t => triggerCollection.Remove(t)); + + var newTrigger = new KeyTrigger() { Key = key }; + newTrigger.Actions.Add(new ChangePropertyAction() { TargetObject = this, PropertyName = nameof(CurrentInputObject), Value = obj }); + triggerCollection.Add(newTrigger); + NewTriggers.Add(newTrigger); + } + + // Add mouse click events directly on the GlView. + var glTriggers = Interaction.GetTriggers(AssociatedObject.glView); + foreach (var (eventName, action) in ClickTriggers) { + var glTrigger = glTriggers.FirstOrDefault(t => t is EventTrigger et && et.EventName == eventName); + + if (glTrigger is null) { + glTrigger = new EventTrigger(eventName); + glTriggers.Add(glTrigger); + } + + glTrigger.Actions.Insert(0, action); + } + } + + protected override void OnDetaching() + { + var triggerCollection = Interaction.GetTriggers(AssociatedObject); + foreach (var trigger in NewTriggers) { + triggerCollection.Remove(trigger); + } + + foreach (var trigger in OldTriggers) { + triggerCollection.Add(trigger); + } + + NewTriggers.Clear(); + OldTriggers.Clear(); + + var glTriggers = Interaction.GetTriggers(AssociatedObject.glView); + foreach (var (eventName, action) in ClickTriggers) { + var glTrigger = glTriggers.First(t => t is EventTrigger et && et.EventName == eventName); + glTrigger.Actions.Remove(action); + } + } + + private void MouseMove(MouseEventArgs args) + { + if (CurrentInputObject is null) + return; + if (AssociatedObject.DataContext is not FumenVisualEditorViewModel editor) + return; + + if (Mouse.RightButton == MouseButtonState.Pressed) { + var cursor = editor.CurrentCursorPosition!.Value; + + if ((cursor.ToSystemNumericsVector2() - editor.SelectionStartPosition).Length() > 15) { + // Begin range mode + editor.SelectionVisibility = Visibility.Visible; + } + + editor.SelectionCurrentCursorPosition = new((float)cursor.X, (float)cursor.Y); + } + } + + private void MouseDown(MouseButtonEventArgs args) + { + if (AssociatedObject.DataContext is not FumenVisualEditorViewModel editor) + return; + + var cursor = editor.CurrentCursorPosition!.Value; + + if (args.ChangedButton == MouseButton.Left) { + args.Handled = true; + } else if (args.ChangedButton == MouseButton.Right) { + editor.SelectionStartPosition = new((float)cursor.X, (float)cursor.Y); + args.Handled = true; + } + } + + private void MouseUp(MouseButtonEventArgs args) + { + var editor = IoC.Get()?.CurrentActivatedEditor; + if (editor is null) + return; + + if (args.ChangedButton == MouseButton.Left) { + PerformBrush(); + args.Handled = true; + } else if (args.ChangedButton == MouseButton.Right) { + if (editor.IsRangeSelecting) { + PerformRemoveGroup(); + } + else { + PerformRemove(); + } + + editor.SelectionVisibility = Visibility.Hidden; + args.Handled = true; + } + } + + private void PerformBrush() + { + var ctrl = (Keyboard.Modifiers & ModifierKeys.Control) > 0; + var alt = (Keyboard.Modifiers & ModifierKeys.Alt) > 0; + + var editor = (FumenVisualEditorViewModel)AssociatedObject.DataContext; + + OngekiTimelineObjectBase ongekiObject = null; + + var objectName = CurrentInputObject?.DisplayName ?? ClipboardObjectName; + editor.UndoRedoManager.ExecuteAction(new LambdaUndoAction($"Brush {objectName}", Redo, Undo)); + + return; + + void Redo() + { + if (CurrentInputObject is null) { + if (!Clipboard.ContainPastableObjects + || !Clipboard.CurrentCopiedObjects.IsOnlyOne(out var clipboardObj) + || clipboardObj is not OngekiTimelineObjectBase) { + return; + } + ongekiObject = (OngekiTimelineObjectBase)clipboardObj.CopyNew(); + } + else { + ongekiObject = CurrentInputObject.GenerateObject(); + if (ctrl && CurrentInputObject.ModifyObjectCtrl is { } modCtrl) + modCtrl.Function.Invoke(ongekiObject); + if (alt && CurrentInputObject.ModifyObjectAlt is { } modAlt) + modAlt.Function.Invoke(ongekiObject); + } + + editor!.MoveObjectTo(ongekiObject, editor.CurrentCursorPosition!.Value); + editor.Fumen.AddObject(ongekiObject); + editor.InteractiveManager.GetInteractive(ongekiObject).OnMoveCanvas(ongekiObject, editor.CurrentCursorPosition.Value, editor); + if (CurrentInputObject?.AddToSelection ?? false) + editor.NotifyObjectClicked(ongekiObject); + } + + void Undo() + { + if (ongekiObject is null) + return; + + editor!.RemoveObject(ongekiObject); + ongekiObject = null; + } + } + + private void PerformRemove() + { + if (CurrentInputObject is null) + return; + + var editor = (FumenVisualEditorViewModel)AssociatedObject.DataContext; + var hit = editor.GetHits() + .Where(kv => + kv.Value.Contains(editor.CurrentCursorPosition!.Value) && + kv.Key.GetType() == CurrentInputObject.ObjectType) + .Select(kv => kv.Key).MinBy(o => o.Id); + + if (hit is not null) { + editor.UndoRedoManager.ExecuteAction(new LambdaUndoAction("Brush delete", Redo, Undo)); + } + + return; + + void Redo() + { + editor.RemoveObject(hit); + } + + void Undo() + { + editor.Fumen.AddObject(hit); + } + } + + private void PerformRemoveGroup() + { + if (CurrentInputObject is null) + return; + + var editor = (FumenVisualEditorViewModel)AssociatedObject.DataContext; + var hits = editor.GetRangeObjects().Where(o => o.GetType() == CurrentInputObject.ObjectType).ToArray(); + + if (hits.Length == 0) { + return; + } + + editor.UndoRedoManager.ExecuteAction(new LambdaUndoAction("Brush delete range", Redo, Undo)); + + return; + + void Redo() + { + foreach (var hit in hits) { + editor.RemoveObject(hit); + } + } + + void Undo() + { + editor.Fumen.AddObjects(hits); + } + } + + private string ClipboardObjectName => "Clipboard"; + + #region Dependency property + + public static readonly DependencyProperty CurrentInputObjectProperty = DependencyProperty.RegisterAttached(nameof(CurrentInputObject), typeof(BrushModeInputObject), typeof(BrushModeBehavior), new PropertyMetadata(null)); + + #endregion + +} + +public class BrushModeInputObjectNameConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return ((BrushModeInputObject)value)?.DisplayName ?? "Clipboard"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/BrushMode/BrushModeInputObject.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/BrushMode/BrushModeInputObject.cs new file mode 100644 index 00000000..23a93771 --- /dev/null +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/BrushMode/BrushModeInputObject.cs @@ -0,0 +1,125 @@ +using System; +using OngekiFumenEditor.Base; +using OngekiFumenEditor.Base.OngekiObjects; +using OngekiFumenEditor.Base.OngekiObjects.Lane; +using OngekiFumenEditor.Base.OngekiObjects.Lane.Base; +using OngekiFumenEditor.Base.OngekiObjects.Wall; + +namespace OngekiFumenEditor.Modules.FumenVisualEditor.Behaviors.BrushMode; + +public class ObjectModificationAction(Action modifier, string description) +{ + public string Description { get; } = description; + public Action Function { get; } = modifier; +} + +public abstract class BrushModeInputObject +{ + public abstract string DisplayName { get; } + public abstract Type ObjectType { get; } + public abstract OngekiTimelineObjectBase GenerateObject(); + + public virtual bool AddToSelection => false; + + public virtual ObjectModificationAction ModifyObjectCtrl { get; } = null; + public virtual ObjectModificationAction ModifyObjectAlt { get; } = null; +} + +public abstract class BrushModeInputObject : BrushModeInputObject + where T : OngekiTimelineObjectBase +{ + public override Type ObjectType => typeof(T); + + public override OngekiTimelineObjectBase GenerateObject() + { + return Activator.CreateInstance(); + } +} + +public abstract class BrushModeInputLane : BrushModeInputObject + where T : LaneStartBase, new() +{ + public override bool AddToSelection => true; +} + +public class BrushModeInputLaneLeft : BrushModeInputLane +{ + public override string DisplayName => "LaneLeft"; +} + +public class BrushModeInputLaneCenter : BrushModeInputLane +{ + public override string DisplayName => "LaneCenter"; +} + +public class BrushModeInputLaneRight : BrushModeInputLane +{ + public override string DisplayName => "LaneRight"; +} + +public class BrushModeInputWallRight : BrushModeInputLane +{ + public override string DisplayName => "WallRight"; +} + +public class BrushModeInputWallLeft : BrushModeInputLane +{ + public override string DisplayName => "WallLeft"; +} + +public class BrushModeInputLaneColorful : BrushModeInputLane +{ + public override string DisplayName => "LaneColorful"; +} + +public abstract class BrushModeInputHitObject : BrushModeInputObject + where T : OngekiTimelineObjectBase, ICriticalableObject +{ + public override ObjectModificationAction ModifyObjectAlt { get; } = new(CritObject, "Set critical"); + + private static void CritObject(OngekiObjectBase baseObject) + { + ((ICriticalableObject)baseObject).IsCritical = true; + } +} + +public class BrushModeInputTap : BrushModeInputHitObject +{ + public override string DisplayName => "Tap"; +} + +public class BrushModeInputHold : BrushModeInputHitObject +{ + public override string DisplayName => "Hold"; + public override bool AddToSelection => true; +} + +public class BrushModeInputFlick : BrushModeInputHitObject +{ + public override ObjectModificationAction ModifyObjectCtrl { get; } = new(SwitchFlick, "Switch direction"); + + private static void SwitchFlick(OngekiObjectBase baseObject) + { + ((Flick)baseObject).Direction = Flick.FlickDirection.Right; + } + + public override string DisplayName => "Flick"; +} + +public class BrushModeInputLaneBlock : BrushModeInputObject +{ + public override ObjectModificationAction ModifyObjectCtrl { get; } = + new ObjectModificationAction(SwitchDirection, "Switch direction"); + + private static void SwitchDirection(OngekiObjectBase baseObject) + { + ((LaneBlockArea)baseObject).Direction = LaneBlockArea.BlockDirection.Right; + } + + public override string DisplayName => "LaneBlock"; +} + +public class BrushModeInputNormalBell : BrushModeInputObject +{ + public override string DisplayName => "Bell"; +} diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/CommandActionMessage.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/CommandActionMessage.cs deleted file mode 100644 index ed200179..00000000 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Behaviors/CommandActionMessage.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Input; -using Caliburn.Micro; -using Gemini.Framework.Commands; -using Microsoft.Xaml.Behaviors; - -namespace OngekiFumenEditor.Modules.FumenVisualEditor.Base; - -public class CommandActionMessage : TriggerAction -{ - public Type CommandDefinition - { - get => (Type)GetValue(CommandDefinitionProperty); - set => SetValue(CommandDefinitionProperty, value); - } - - protected override void Invoke(object parameter) - { - var commandService = IoC.Get(); - var definition = commandService.GetCommandDefinition(CommandDefinition); - var command = commandService.GetTargetableCommand(commandService.GetCommand(definition)); - if (command.CanExecute(parameter)) - command.Execute(parameter); - } - - public static readonly DependencyProperty CommandDefinitionProperty = DependencyProperty.Register("CommandDefinition", typeof(Type), typeof(CommandActionMessage), null); -} \ No newline at end of file diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs deleted file mode 100644 index 26baabd9..00000000 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandDefinition.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Gemini.Framework.Commands; -using OngekiFumenEditor.Base; -using OngekiFumenEditor.Base.OngekiObjects; -using OngekiFumenEditor.Base.OngekiObjects.Lane; -using OngekiFumenEditor.Base.OngekiObjects.Wall; -using OngekiFumenEditor.Utils; - -namespace OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject; - -public abstract class AddObjectCommandDefinition : CommandDefinition -{ - public abstract OngekiMovableObjectBase CreateOngekiObject(); - public abstract string ObjectName { get; } - public virtual bool AddToSelection { get; } = false; - - public override string Name => $"AddObject_{ObjectName}"; - public override string Text => $"Add {ObjectName}"; - public override string ToolTip => - AddToSelection ? $"Add a new {ObjectName} to the fumen and select it" : $"Add a new {ObjectName} to the fumen"; -} - -[CommandDefinition] -public abstract class AddLaneCommandDefinition : AddObjectCommandDefinition -{ - public override bool AddToSelection { get; } = true; -} - -[CommandDefinition] -public class AddLaneLeftCommandDefinition : AddLaneCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new LaneLeftStart(); - public override string ObjectName => "LaneLeft"; -} - -[CommandDefinition] -public class AddLaneCenterCommandDefinition : AddLaneCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new LaneCenterStart(); - public override string ObjectName => "LaneCenter"; -} - -[CommandDefinition] -public class AddLaneRightCommandDefinition : AddLaneCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new LaneRightStart(); - public override string ObjectName => "LaneRight"; -} - -[CommandDefinition] -public class AddWallRightCommandDefinition : AddLaneCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new WallRightStart(); - public override string ObjectName => "WallRight"; -} - -[CommandDefinition] -public class AddWallLeftCommandDefinition : AddLaneCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new WallLeftStart(); - public override string ObjectName => "WallLeft"; -} - -[CommandDefinition] -public class AddLaneColorfulCommandDefinition : AddLaneCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new ColorfulLaneStart(); - public override string ObjectName => "LaneColorful"; -} - -[CommandDefinition] -public class AddTapCommandDefinition : AddObjectCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new Tap(); - public override string ObjectName => "Tap"; -} - -[CommandDefinition] -public class AddHoldCommandDefinition : AddObjectCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new Hold(); - public override string ObjectName => "Hold"; - public override bool AddToSelection => true; -} - -[CommandDefinition] -public class AddFlickCommandDefinition : AddObjectCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new Flick(); - public override string ObjectName => "Flick"; -} - -[CommandDefinition] -public class AddFlickReversedCommandDefinition : AddFlickCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new Flick() { Direction = Flick.FlickDirection.Right }; -} - -[CommandDefinition] -public class AddLaneBlockCommandDefinition : AddObjectCommandDefinition -{ - public override OngekiMovableObjectBase CreateOngekiObject() => new Flick(); - public override string ObjectName => "LaneBlock"; -} diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs deleted file mode 100644 index 9eb9aaa8..00000000 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/AddObject/AddObjectCommandHandler.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading.Tasks; -using Caliburn.Micro; -using Gemini.Framework.Commands; -using Gemini.Modules.UndoRedo; -using OngekiFumenEditor.Base; -using OngekiFumenEditor.Modules.FumenVisualEditor.Base; -using OngekiFumenEditor.Modules.FumenVisualEditor.Kernel; -using OngekiFumenEditor.Modules.FumenVisualEditor.Views; -using OngekiFumenEditor.Utils; - -namespace OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject; - -[CommandHandler] -public class AddObjectCommandHandler : CommandHandlerBase where D : AddObjectCommandDefinition -{ - private OngekiMovableObjectBase OngekiObject = null; - - public override void Update(Command command) - { - base.Update(command); - command.Enabled = true; - } - - public override Task Run(Command command) - { - var cmd = (AddObjectCommandDefinition)command.CommandDefinition; - var editor = IoC.Get()?.CurrentActivatedEditor; - - editor?.UndoRedoManager.ExecuteAction(new LambdaUndoAction(command.CommandDefinition.Name, Redo, Undo)); - - return Task.CompletedTask; - - void Redo() - { - OngekiObject = cmd.CreateOngekiObject(); - editor!.MoveObjectTo(OngekiObject, editor.CurrentCursorPosition!.Value); - editor.Fumen.AddObject(OngekiObject); - editor.InteractiveManager.GetInteractive(OngekiObject).OnMoveCanvas(OngekiObject, editor.CurrentCursorPosition.Value, editor); - if (cmd.AddToSelection) - editor.NotifyObjectClicked(OngekiObject); - } - - void Undo() - { - editor!.RemoveObject(OngekiObject); - OngekiObject = null; - } - } -} - -[CommandHandler] public class AddObjectLaneLeftCommandHandler : AddObjectCommandHandler { } -[CommandHandler] public class AddObjectLaneCenterCommandHandler : AddObjectCommandHandler { } -[CommandHandler] public class AddObjectLaneRightCommandHandler : AddObjectCommandHandler { } -[CommandHandler] public class AddObjectWallLeftCommandHandler : AddObjectCommandHandler { } -[CommandHandler] public class AddObjectWallRightCommandHandler : AddObjectCommandHandler { } -[CommandHandler] public class AddObjectLaneColorfulCommandHandler : AddObjectCommandHandler { } -[CommandHandler] public class AddObjectTapCommandHandler : AddObjectCommandHandler { } -[CommandHandler] public class AddObjectHoldCommandHandler : AddObjectCommandHandler { } -[CommandHandler] public class AddObjectFlickCommandHandler : AddObjectCommandHandler { } -[CommandHandler] public class AddObjectFlickReversedCommandHandler : AddObjectCommandHandler { } diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs index b589ed3d..28540947 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs @@ -1,11 +1,13 @@ -using Gemini.Framework.Commands; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using Gemini.Framework.Commands; using Gemini.Framework.Threading; +using Microsoft.Xaml.Behaviors; using OngekiFumenEditor.Modules.FumenVisualEditor.Kernel; using OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels; -using System.ComponentModel.Composition; -using System.Threading.Tasks; +using OngekiFumenEditor.Modules.FumenVisualEditor.Views; -namespace OngekiFumenEditor.Modules.FumenVisualEditor.Commands.BrushModeSwitch +namespace OngekiFumenEditor.Modules.FumenVisualEditor.Commands.BrushModeSwitch { [CommandHandler] public class BrushModeSwitchCommandHandler : CommandHandlerBase @@ -25,11 +27,21 @@ public override void Update(Command command) command.Checked = editorDocumentManager.CurrentActivatedEditor?.BrushMode ?? false; } - public override Task Run(Command command) - { - if (editorDocumentManager.CurrentActivatedEditor is FumenVisualEditorViewModel editor) - editor.BrushMode = !editor.BrushMode; - return TaskUtility.Completed; - } - } + public override Task Run(Command command) + { + if (editorDocumentManager.CurrentActivatedEditor is FumenVisualEditorViewModel editor) { + editor.BrushMode = !editor.BrushMode; + + if (editor.BrushMode) { + Interaction.GetBehaviors((FumenVisualEditorView)editor.GetView()).Add(editor.BrushModeBehavior); + } + else { + var behaviors = Interaction.GetBehaviors((FumenVisualEditorView)editor.GetView()); + behaviors.Remove(editor.BrushModeBehavior); + } + } + + return TaskUtility.Completed; + } + } } \ No newline at end of file diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawSelectingRangeHelper.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawSelectingRangeHelper.cs index f31daa45..d707ba36 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawSelectingRangeHelper.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawSelectingRangeHelper.cs @@ -9,11 +9,15 @@ namespace OngekiFumenEditor.Modules.FumenVisualEditor.Graphics.Drawing.Editors { public class DrawSelectingRangeHelper { + private static Vector4 LineColorSelect = new(1, 0, 1, 1); + private static Vector4 FillColorSelect = new(1, 1, 1, 0.15f); + + private static Vector4 LineColorDelete = new(1, 0.1f, 0.1f, 1); + private static Vector4 FillColorDelete = new(1, 0.1f, 0.1f, 0.15f); + private ISimpleLineDrawing lineDrawing; private IPolygonDrawing polygonDrawing; - private Vector4 lineColor = new(1, 0, 1, 1); - private Vector4 fillColor = new(1, 1, 1, 0.15f); private VertexDash dash = new() { DashSize = 8, @@ -31,6 +35,17 @@ public void Draw(IFumenEditorDrawingContext target) if (target.Editor.SelectionVisibility != System.Windows.Visibility.Visible) return; + Vector4 lineColor, fillColor; + if (target.Editor.BrushMode) { + // If this is used during brush mode, it is for the deletion rectangle + lineColor = LineColorDelete; + fillColor = FillColorDelete; + } + else { + lineColor = LineColorSelect; + fillColor = FillColorSelect; + } + var topY = Math.Max(target.Editor.SelectionCurrentCursorPosition.Y, target.Editor.SelectionStartPosition.Y); var buttomY = Math.Min(target.Editor.SelectionCurrentCursorPosition.Y, target.Editor.SelectionStartPosition.Y); var rightX = Math.Max(target.Editor.SelectionCurrentCursorPosition.X, target.Editor.SelectionStartPosition.X); diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs index 4282106b..ef60282e 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.Brush.cs @@ -1,27 +1,15 @@ -using System.Windows.Input; -using Caliburn.Micro; using Gemini.Framework; -using Gemini.Framework.Commands; -using OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject; -using OngekiFumenEditor.Utils; +using OngekiFumenEditor.Modules.FumenVisualEditor.Behaviors.BrushMode; namespace OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels; public partial class FumenVisualEditorViewModel : PersistedDocument { - private void RunCommand(CommandDefinition def) + private BrushModeBehavior _brushModeBehavior = new(); + + public BrushModeBehavior BrushModeBehavior { - new TargetableCommand(new(def)).Execute(null); + get => _brushModeBehavior; + set => Set(ref _brushModeBehavior, value); } - - public void Brush_AddNewLaneLeft() - => RunCommand(new AddLaneLeftCommandDefinition()); - public void Brush_AddNewLaneCenter() - => RunCommand(new AddLaneCenterCommandDefinition()); - public void Brush_AddNewLaneRight() - => RunCommand(new AddLaneRightCommandDefinition()); - public void Brush_AddNewWallLeft() - => RunCommand(new AddWallLeftCommandDefinition()); - public void Brush_AddNewWallRight() - => RunCommand(new AddWallRightCommandDefinition()); } \ No newline at end of file diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs index 7c6470d2..2ef61d7f 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs @@ -5,12 +5,10 @@ using Gemini.Modules.Toolbox; using Gemini.Modules.Toolbox.Models; using Gemini.Modules.UndoRedo; -using Microsoft.CodeAnalysis.Differencing; using OngekiFumenEditor.Base; using OngekiFumenEditor.Base.EditorObjects; using OngekiFumenEditor.Base.EditorObjects.LaneCurve; using OngekiFumenEditor.Base.OngekiObjects; -using OngekiFumenEditor.Base.OngekiObjects.BulletPalleteEnums; using OngekiFumenEditor.Base.OngekiObjects.ConnectableObject; using OngekiFumenEditor.Modules.AudioPlayerToolViewer; using OngekiFumenEditor.Modules.FumenObjectPropertyBrowser; @@ -25,18 +23,16 @@ using OngekiFumenEditor.Utils; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Windows; -using System.Windows.Documents; using System.Windows.Input; -using OngekiFumenEditor.Base.EditorObjects.Svg; using OngekiFumenEditor.Base.OngekiObjects.Lane; using OngekiFumenEditor.Base.OngekiObjects.Lane.Base; using OngekiFumenEditor.Base.OngekiObjects.Wall; -using OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels.OngekiObjects; namespace OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels { @@ -166,6 +162,8 @@ public Point? CurrentCursorPosition public ObjectInteractiveManager InteractiveManager { get; private set; } = new(); + public ImmutableDictionary GetHits() => hits.ToImmutableDictionary(); + #region provide extra MenuItem by plugins public void InitExtraMenuItems() @@ -470,6 +468,20 @@ private void SelectRangeObjects() return; } + var selectObjects = GetRangeObjects().ToArray(); + + if (selectObjects.Length == 1) + NotifyObjectClicked(selectObjects.FirstOrDefault()); + else + { + foreach (var o in selectObjects.OfType()) + o.IsSelected = true; + IoC.Get().RefreshSelected(this); + } + } + + public IEnumerable GetRangeObjects() + { var topY = Math.Max(SelectionCurrentCursorPosition.Y, SelectionStartPosition.Y); var buttomY = Math.Min(SelectionCurrentCursorPosition.Y, SelectionStartPosition.Y); var rightX = Math.Max(SelectionCurrentCursorPosition.X, SelectionStartPosition.X); @@ -480,7 +492,12 @@ private void SelectRangeObjects() var minXGrid = XGridCalculator.ConvertXToXGrid(leftX, this); var maxXGrid = XGridCalculator.ConvertXToXGrid(rightX, this); - bool check(OngekiObjectBase obj) + return Fumen.GetAllDisplayableObjects() + .OfType() + .Distinct() + .Where(Check); + + bool Check(OngekiObjectBase obj) { if (obj is ITimelineObject timelineObject) { @@ -496,21 +513,6 @@ bool check(OngekiObjectBase obj) return true; } - - var selectObjects = Fumen.GetAllDisplayableObjects() - .OfType() - .Distinct() - .Where(check) - .ToArray(); - - if (selectObjects.Length == 1) - NotifyObjectClicked(selectObjects.FirstOrDefault()); - else - { - foreach (var o in selectObjects.OfType()) - o.IsSelected = true; - IoC.Get().RefreshSelected(this); - } } public void OnFocusableChanged(ActionExecutionContext e) @@ -970,7 +972,12 @@ public void OnMouseLeave(ActionExecutionContext e) public void OnMouseUp(ActionExecutionContext e) { - var arg = e.EventArgs as MouseEventArgs; + var arg = e.EventArgs as MouseButtonEventArgs; + if (arg is null || arg.Handled) { + return; + } + + Log.LogInfo("Visual mouseup"); if (IsLocked) return; @@ -1069,6 +1076,10 @@ public void OnMouseUp(ActionExecutionContext e) public void OnMouseDown(ActionExecutionContext e) { var arg = e.EventArgs as MouseEventArgs; + if (arg is null || arg.Handled) { + return; + } + Log.LogInfo("Visual mousedown"); prevRightButtonState = arg.RightButton; @@ -1300,8 +1311,11 @@ public async void OnMouseMove(Point pos) private void TryApplyBrushObject(Point p) { - var copyManager = IoC.Get(); + if (BrushMode && BrushModeBehavior.CurrentInputObject is not null) { + } + var copyManager = IoC.Get(); + if (!(copyManager.CurrentCopiedObjects.IsOnlyOne(out var c) && c is OngekiObjectBase copySouceObj)) return; diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/BrushModeOverlayView.xaml b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/BrushModeOverlayView.xaml new file mode 100644 index 00000000..4ab7de04 --- /dev/null +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/BrushModeOverlayView.xaml @@ -0,0 +1,46 @@ + + + + + 0.0 + 100.0 + + + + + + + + + + + + + + + + + + + + + + diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/BrushModeOverlayView.xaml.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/BrushModeOverlayView.xaml.cs new file mode 100644 index 00000000..27691b08 --- /dev/null +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/BrushModeOverlayView.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace OngekiFumenEditor.Modules.FumenVisualEditor.Views; + +public partial class BrushModeOverlayView : UserControl +{ + public BrushModeOverlayView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml index ca2ab534..163c5977 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml @@ -3,22 +3,16 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cal="http://caliburnmicro.com" - xmlns:common="clr-namespace:Gemini.Framework.Services;assembly=Gemini" - xmlns:controls="clr-namespace:OngekiFumenEditor.UI.Controls" xmlns:controls1="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Views.UI" xmlns:converters="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:markup="clr-namespace:OngekiFumenEditor.UI.Markup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:opentkcontrol="clr-namespace:OpenTK.Wpf;assembly=GLWpfControl" - xmlns:res="clr-namespace:OngekiFumenEditor.Properties" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:valueconverters="clr-namespace:OngekiFumenEditor.UI.ValueConverters" xmlns:viewmodels="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels" - xmlns:base="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Base" - xmlns:addObject="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Commands.AddObject" - xmlns:brushModeSwitch="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Commands.BrushModeSwitch" + xmlns:views="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Views" cal:Message.Attach="[Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" d:DataContext="{d:DesignInstance Type=viewmodels:FumenVisualEditorViewModel}" d:DesignHeight="450" @@ -42,6 +36,7 @@ + @@ -109,6 +102,7 @@ + --> @@ -183,6 +177,10 @@ cal:Message.Attach="[Event MouseWheel]=[Action OnMouseWheel($executionContext)]; [Event SizeChanged] = [Action OnSizeChanged($executionContext)]; [Event Loaded] = [Action OnLoaded($executionContext)]; [Event PreviewMouseDown] = [Action OnMouseDown($executionContext)]; [Event MouseMove] = [Action OnMouseMove($executionContext)]; [Event PreviewMouseUp] = [Action OnMouseUp($executionContext)]; [Event MouseLeave] = [Action OnMouseLeave($executionContext)];" Ready="glView_Ready" SizeChanged="glView_SizeChanged" /> + + +{ + private readonly Action Action; + + public LambdaTriggerAction(System.Action action) + { + Action = action; + } + + protected override void Invoke(object parameter) + { + Action.Invoke(parameter); + } +} \ No newline at end of file diff --git a/OngekiFumenEditor/Utils/Vector2ExtensionMethod.cs b/OngekiFumenEditor/Utils/Vector2ExtensionMethod.cs index 020f1018..149c6949 100644 --- a/OngekiFumenEditor/Utils/Vector2ExtensionMethod.cs +++ b/OngekiFumenEditor/Utils/Vector2ExtensionMethod.cs @@ -13,5 +13,8 @@ public static OpenTK.Mathematics.Vector2 ToOpenTKVector2(this System.Numerics.Ve public static System.Numerics.Vector2 ToSystemNumericsVector2(this OpenTK.Mathematics.Vector2 p) => new (p.X, p.Y); + + public static System.Numerics.Vector2 ToSystemNumericsVector2(this System.Windows.Point p) + => new((float)p.X, (float)p.Y); } } From a0ad474cfc2dec494a5e9733d8ae897bb88a6aa0 Mon Sep 17 00:00:00 2001 From: Zach Day Date: Sat, 19 Oct 2024 23:13:26 -0400 Subject: [PATCH 06/67] Remove BrushMode variable --- .../BrushModeSwitchCommandHandler.cs | 42 +++++----- ...lEditorViewModel.UserInteractionActions.cs | 84 +------------------ .../ViewModels/FumenVisualEditorViewModel.cs | 25 ++---- 3 files changed, 33 insertions(+), 118 deletions(-) diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs index 28540947..417e80c9 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs @@ -6,39 +6,41 @@ using OngekiFumenEditor.Modules.FumenVisualEditor.Kernel; using OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels; using OngekiFumenEditor.Modules.FumenVisualEditor.Views; +using OngekiFumenEditor.Properties; namespace OngekiFumenEditor.Modules.FumenVisualEditor.Commands.BrushModeSwitch { - [CommandHandler] - public class BrushModeSwitchCommandHandler : CommandHandlerBase - { - private readonly IEditorDocumentManager editorDocumentManager; + [CommandHandler] + public class BrushModeSwitchCommandHandler : CommandHandlerBase + { + private readonly IEditorDocumentManager editorDocumentManager; - [ImportingConstructor] - public BrushModeSwitchCommandHandler(IEditorDocumentManager editorDocumentManager) - { - this.editorDocumentManager = editorDocumentManager; - } + [ImportingConstructor] + public BrushModeSwitchCommandHandler(IEditorDocumentManager editorDocumentManager) + { + this.editorDocumentManager = editorDocumentManager; + } - public override void Update(Command command) - { - base.Update(command); - command.Enabled = editorDocumentManager.CurrentActivatedEditor is not null; - command.Checked = editorDocumentManager.CurrentActivatedEditor?.BrushMode ?? false; - } + public override void Update(Command command) + { + base.Update(command); + command.Enabled = editorDocumentManager.CurrentActivatedEditor is not null; + command.Checked = editorDocumentManager.CurrentActivatedEditor?.BrushMode ?? false; + } public override Task Run(Command command) { if (editorDocumentManager.CurrentActivatedEditor is FumenVisualEditorViewModel editor) { - editor.BrushMode = !editor.BrushMode; - + var behaviors = Interaction.GetBehaviors((FumenVisualEditorView)editor.GetView()); + if (editor.BrushMode) { - Interaction.GetBehaviors((FumenVisualEditorView)editor.GetView()).Add(editor.BrushModeBehavior); + behaviors.Remove(editor.BrushModeBehavior); } else { - var behaviors = Interaction.GetBehaviors((FumenVisualEditorView)editor.GetView()); - behaviors.Remove(editor.BrushModeBehavior); + behaviors.Add(editor.BrushModeBehavior); } + editor.NotifyOfPropertyChange(nameof(FumenVisualEditorViewModel.BrushMode)); + editor.ToastNotify($"{Resources.BrushMode}{(editor.BrushMode ? Resources.Enable : Resources.Disable)}"); } return TaskUtility.Completed; diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs index 2ef61d7f..c3ce48a8 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs @@ -1014,20 +1014,10 @@ public void OnMouseUp(ActionExecutionContext e) var compositeAction = UndoRedoManager.EndCombineAction(Resources.DragObjects); UndoRedoManager.ExecuteAction(compositeAction); } - else - { + else { //Log.LogDebug($"mouseDownHitObject = {mouseDownHitObject?.ReferenceOngekiObject}"); //if no object clicked or alt is pressing , just to process as brush actions. - if (mouseDownHitObject is null || Keyboard.Modifiers.HasFlag(ModifierKeys.Alt)) - { - //for object brush - if (BrushMode) - { - TryApplyBrushObject(pos); - } - } - else - { + if (mouseDownHitObject is not null && !Keyboard.Modifiers.HasFlag(ModifierKeys.Alt)) { if (mouseDownHitObjectPosition is Point p) mouseDownHitObject = NotifyObjectClicked(mouseDownHitObject, mouseDownNextHitObject); } @@ -1117,11 +1107,6 @@ public void OnMouseDown(ActionExecutionContext e) hitResult = hitResult.Concat(lanes).Distinct().ToList(); } - if (BrushMode) - { - //笔刷模式下,忽略点击线段和节点~ - hitResult.RemoveAll(x => x is ConnectableObjectBase); - } var idx = Math.Max(0, hitResult.IndexOf(mouseDownHitObject)); var hitOngekiObject = hitResult.ElementAtOrDefault(idx); @@ -1309,72 +1294,7 @@ public async void OnMouseMove(Point pos) } } - private void TryApplyBrushObject(Point p) - { - if (BrushMode && BrushModeBehavior.CurrentInputObject is not null) { - } - var copyManager = IoC.Get(); - - if (!(copyManager.CurrentCopiedObjects.IsOnlyOne(out var c) && c is OngekiObjectBase copySouceObj)) - return; - - var newObject = copySouceObj.CopyNew(); - if (newObject is null - //不支持笔刷模式下新建以下玩意 - || newObject is ConnectableStartObject) - { - ToastNotify(Resources.NotSupportInBrushMode.Format(copySouceObj?.Name)); - return; - } - - p.Y = ViewHeight - p.Y + Rect.MinY; - var v = new Vector2((float)p.X, (float)p.Y); - - System.Action undo = () => - { - if (newObject is ConnectableChildObjectBase childObject) - { - (copySouceObj as ConnectableChildObjectBase)?.ReferenceStartObject.RemoveChildObject(childObject); - } - else - { - RemoveObject(newObject); - } - }; - - System.Action redo = async () => - { - InteractiveManager.GetInteractive(newObject).OnMoveCanvas(newObject, p, this); - var x = newObject is IHorizonPositionObject horizonPositionObject ? XGridCalculator.ConvertXGridToX(horizonPositionObject.XGrid, this) : 0; - var y = newObject is ITimelineObject timelineObject ? TGridCalculator.ConvertTGridToY_DesignMode(timelineObject.TGrid, this) : 0; - var dist = Vector2.Distance(v, new Vector2((float)x, (float)y)); - if (dist > 20) - { - Log.LogDebug($"dist : {dist:F2} > 20 , undo&&discard"); - undo(); - - Mouse.OverrideCursor = Cursors.No; - await Task.Delay(100); - Mouse.OverrideCursor = Cursors.Arrow; - } - else - { - if (newObject is ConnectableChildObjectBase childObject) - { - //todo there is a bug. - (copySouceObj as ConnectableChildObjectBase)?.ReferenceStartObject.AddChildObject(childObject); - } - else - { - Fumen.AddObject(newObject); - } - } - }; - - UndoRedoManager.ExecuteAction(LambdaUndoAction.Create(Resources.AddObjectsByBrush, redo, undo)); - } - #region Quick Add Actions public void KeyboardAction_AddNewWallLeft(bool clearSelection = true) diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs index dd8c0e98..a17bfc87 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs @@ -1,5 +1,5 @@ using Caliburn.Micro; -using Gemini.Framework; +using Gemini.Framework; using Microsoft.Win32; using OngekiFumenEditor.Base; using OngekiFumenEditor.Base.OngekiObjects.ConnectableObject; @@ -15,17 +15,18 @@ using OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels.Dialogs; using OngekiFumenEditor.Parser; using OngekiFumenEditor.Properties; -using OngekiFumenEditor.Utils; +using OngekiFumenEditor.Utils; using System; using System.ComponentModel; -using System.ComponentModel.Composition; +using System.ComponentModel.Composition; using System.IO; using System.Threading; -using System.Threading.Tasks; +using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; +using Microsoft.Xaml.Behaviors; -namespace OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels +namespace OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels { [Export(typeof(FumenVisualEditorViewModel))] public partial class FumenVisualEditorViewModel : PersistedDocument @@ -149,16 +150,8 @@ public void RecalculateTotalDurationHeight() private bool isSelectRangeDragging; private bool isLeftMouseDown; - private bool brushMode = false; public bool BrushMode - { - get => brushMode; - set - { - Set(ref brushMode, value); - ToastNotify($"{Resources.BrushMode}{(BrushMode ? Resources.Enable : Resources.Disable)}"); - } - } + => Interaction.GetBehaviors((DependencyObject)GetView()).Contains(BrushModeBehavior); private bool isShowCurveControlAlways = false; private bool enableShowPlayerLocation; @@ -342,5 +335,5 @@ public override async Task TryCloseAsync(bool? dialogResult = null) } #endregion - } -} + } +} From d6c73c0d15e61fa1feb00cbb0cb7f2ffe7a0a013 Mon Sep 17 00:00:00 2001 From: Zach Day Date: Sat, 19 Oct 2024 23:13:53 -0400 Subject: [PATCH 07/67] Restore old hotkeys --- .../Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml index 163c5977..a84a4e52 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml @@ -13,7 +13,7 @@ xmlns:valueconverters="clr-namespace:OngekiFumenEditor.UI.ValueConverters" xmlns:viewmodels="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels" xmlns:views="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Views" - cal:Message.Attach="[Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" + cal:Message.Attach="[Key OemTilde] = [Action KeyboardAction_FastPlaceDockableObjectToWallLeft]; [Key D4] = [Action KeyboardAction_FastPlaceDockableObjectToWallRight]; [Key D3] = [Action KeyboardAction_FastPlaceDockableObjectToRight]; [Key D2] = [Action KeyboardAction_FastPlaceDockableObjectToCenter]; [Key D1] = [Action KeyboardAction_FastPlaceDockableObjectToLeft]; [Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" d:DataContext="{d:DesignInstance Type=viewmodels:FumenVisualEditorViewModel}" d:DesignHeight="450" d:DesignWidth="800" From 2cd4afe5507c617d135cc38ea62cb3e84a3eee83 Mon Sep 17 00:00:00 2001 From: Zach Day Date: Sat, 19 Oct 2024 23:54:00 -0400 Subject: [PATCH 08/67] Restore old numerical keybinds --- .../BrushModeSwitch/BrushModeSwitchCommandHandler.cs | 8 ++++---- .../Graphics/Drawing/Editors/DrawSelectingRangeHelper.cs | 2 +- .../FumenVisualEditorViewModel.UserInteractionActions.cs | 8 ++++++++ .../ViewModels/FumenVisualEditorViewModel.cs | 9 ++++++--- .../FumenVisualEditor/Views/FumenVisualEditorView.xaml | 4 ++-- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs index 417e80c9..649cb525 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Commands/BrushModeSwitch/BrushModeSwitchCommandHandler.cs @@ -25,7 +25,7 @@ public override void Update(Command command) { base.Update(command); command.Enabled = editorDocumentManager.CurrentActivatedEditor is not null; - command.Checked = editorDocumentManager.CurrentActivatedEditor?.BrushMode ?? false; + command.Checked = editorDocumentManager.CurrentActivatedEditor?.IsBrushMode ?? false; } public override Task Run(Command command) @@ -33,14 +33,14 @@ public override Task Run(Command command) if (editorDocumentManager.CurrentActivatedEditor is FumenVisualEditorViewModel editor) { var behaviors = Interaction.GetBehaviors((FumenVisualEditorView)editor.GetView()); - if (editor.BrushMode) { + if (editor.IsBrushMode) { behaviors.Remove(editor.BrushModeBehavior); } else { behaviors.Add(editor.BrushModeBehavior); } - editor.NotifyOfPropertyChange(nameof(FumenVisualEditorViewModel.BrushMode)); - editor.ToastNotify($"{Resources.BrushMode}{(editor.BrushMode ? Resources.Enable : Resources.Disable)}"); + editor.NotifyOfPropertyChange(nameof(FumenVisualEditorViewModel.IsBrushMode)); + editor.ToastNotify($"{Resources.BrushMode}{(editor.IsBrushMode ? Resources.Enable : Resources.Disable)}"); } return TaskUtility.Completed; diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawSelectingRangeHelper.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawSelectingRangeHelper.cs index d707ba36..012b6e35 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawSelectingRangeHelper.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Graphics/Drawing/Editors/DrawSelectingRangeHelper.cs @@ -36,7 +36,7 @@ public void Draw(IFumenEditorDrawingContext target) return; Vector4 lineColor, fillColor; - if (target.Editor.BrushMode) { + if (target.Editor.IsBrushMode) { // If this is used during brush mode, it is for the deletion rectangle lineColor = LineColorDelete; fillColor = FillColorDelete; diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs index c3ce48a8..7d959472 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs @@ -30,9 +30,11 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Input; +using Gemini.Framework.Commands; using OngekiFumenEditor.Base.OngekiObjects.Lane; using OngekiFumenEditor.Base.OngekiObjects.Lane.Base; using OngekiFumenEditor.Base.OngekiObjects.Wall; +using OngekiFumenEditor.Modules.FumenVisualEditor.Commands.BrushModeSwitch; namespace OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels { @@ -798,6 +800,12 @@ void ChangeFlicks() } } + public void KeyboardAction_SwitchBrushModeAction() + { + var command = IoC.Get().GetCommand(new BrushModeSwitchCommandDefinition()); + CommandRouterHelper.ExecuteCommand(command).Wait(); + } + public bool CheckAndNotifyIfPlaceBeyondDuration(Point placePoint) { if (placePoint.Y > TotalDurationHeight || placePoint.Y < 0) diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs index a17bfc87..0401bb9e 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs @@ -24,6 +24,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; +using Gemini.Framework.Commands; using Microsoft.Xaml.Behaviors; namespace OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels @@ -150,9 +151,6 @@ public void RecalculateTotalDurationHeight() private bool isSelectRangeDragging; private bool isLeftMouseDown; - public bool BrushMode - => Interaction.GetBehaviors((DependencyObject)GetView()).Contains(BrushModeBehavior); - private bool isShowCurveControlAlways = false; private bool enableShowPlayerLocation; @@ -166,8 +164,13 @@ public bool IsShowCurveControlAlways } } + public bool IsBrushMode + => Interaction.GetBehaviors((DependencyObject)GetView()).Contains(BrushModeBehavior); + public EditorSetting Setting { get; } = new EditorSetting(); + [Import] public ICommandService CommandService; + public FumenVisualEditorViewModel() : base() { //replace owned impl diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml index a84a4e52..2d5268b2 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml @@ -13,7 +13,7 @@ xmlns:valueconverters="clr-namespace:OngekiFumenEditor.UI.ValueConverters" xmlns:viewmodels="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels" xmlns:views="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.Views" - cal:Message.Attach="[Key OemTilde] = [Action KeyboardAction_FastPlaceDockableObjectToWallLeft]; [Key D4] = [Action KeyboardAction_FastPlaceDockableObjectToWallRight]; [Key D3] = [Action KeyboardAction_FastPlaceDockableObjectToRight]; [Key D2] = [Action KeyboardAction_FastPlaceDockableObjectToCenter]; [Key D1] = [Action KeyboardAction_FastPlaceDockableObjectToLeft]; [Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" + cal:Message.Attach="[Key OemTilde] = [Action KeyboardAction_FastPlaceDockableObjectToWallLeft]; [Key D4] = [Action KeyboardAction_FastPlaceDockableObjectToWallRight]; [Key D3] = [Action KeyboardAction_FastPlaceDockableObjectToRight]; [Key D2] = [Action KeyboardAction_FastPlaceDockableObjectToCenter]; [Key D1] = [Action KeyboardAction_FastPlaceDockableObjectToLeft]; [Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Key B] = [KeyboardAction_SwitchBrushModeAction()]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; [Key PageUp] = [Action ScrollPage(1)]; [Key PageDown] = [Action ScrollPage(-1)]" d:DataContext="{d:DesignInstance Type=viewmodels:FumenVisualEditorViewModel}" d:DesignHeight="450" d:DesignWidth="800" @@ -180,7 +180,7 @@ + Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type views:FumenVisualEditorView}}, Path=DataContext.IsBrushMode, Converter={StaticResource BoolToVisibilityConverter}}" /> Date: Sun, 20 Oct 2024 15:23:49 +0800 Subject: [PATCH 09/67] implement KeyBinding --- .../KeyBinding/DefaultKeyBindingManager.cs | 143 +++++++++++++++++ .../Kernel/KeyBinding/IKeyBindingManager.cs | 27 ++++ .../Kernel/KeyBinding/KeyBindingDefinition.cs | 117 ++++++++++++++ .../Dialogs/ConfigKeyBindingDialog.xaml | 72 +++++++++ .../Dialogs/ConfigKeyBindingDialog.xaml.cs | 144 ++++++++++++++++++ .../Models/KeyBindingDefinitionWrapper.cs | 26 ++++ .../ShowKeybindExpressionValueConverter.cs | 28 ++++ .../ViewModels/KeyBindingSettingViewModel.cs | 51 +++++++ .../Views/KeyBindingSettingView.xaml | 56 +++++++ .../Views/KeyBindingSettingView.xaml.cs | 15 ++ .../KeyBindingDefinitions.cs | 19 +++ OngekiFumenEditor/OngekiFumenEditor.csproj | 9 ++ .../Properties/KeyBindingSetting.Designer.cs | 26 ++++ .../Properties/KeyBindingSetting.settings | 5 + .../Input/ActionMessageKeyBinding.cs | 102 +++++++++++++ .../UI/KeyBinding/Input/KeyBinding.cs | 40 +++++ 16 files changed, 880 insertions(+) create mode 100644 OngekiFumenEditor/Kernel/KeyBinding/DefaultKeyBindingManager.cs create mode 100644 OngekiFumenEditor/Kernel/KeyBinding/IKeyBindingManager.cs create mode 100644 OngekiFumenEditor/Kernel/KeyBinding/KeyBindingDefinition.cs create mode 100644 OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml create mode 100644 OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml.cs create mode 100644 OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Models/KeyBindingDefinitionWrapper.cs create mode 100644 OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ValueConverters/ShowKeybindExpressionValueConverter.cs create mode 100644 OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs create mode 100644 OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml create mode 100644 OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml.cs create mode 100644 OngekiFumenEditor/Modules/FumenVisualEditor/KeyBindingDefinitions.cs create mode 100644 OngekiFumenEditor/Properties/KeyBindingSetting.Designer.cs create mode 100644 OngekiFumenEditor/Properties/KeyBindingSetting.settings create mode 100644 OngekiFumenEditor/UI/KeyBinding/Input/ActionMessageKeyBinding.cs create mode 100644 OngekiFumenEditor/UI/KeyBinding/Input/KeyBinding.cs diff --git a/OngekiFumenEditor/Kernel/KeyBinding/DefaultKeyBindingManager.cs b/OngekiFumenEditor/Kernel/KeyBinding/DefaultKeyBindingManager.cs new file mode 100644 index 00000000..c9b0756c --- /dev/null +++ b/OngekiFumenEditor/Kernel/KeyBinding/DefaultKeyBindingManager.cs @@ -0,0 +1,143 @@ +using OngekiFumenEditor.Properties; +using OngekiFumenEditor.Utils; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace OngekiFumenEditor.Kernel.KeyBinding +{ + [Export(typeof(IKeyBindingManager))] + [PartCreationPolicy(CreationPolicy.Shared)] + internal class DefaultKeyBindingManager : IKeyBindingManager + { + private readonly string jsonConfigFilePath; + + private class Config + { + public Dictionary KeyBindings { get; set; } = new(); + } + + public IEnumerable KeyBindingDefinations => definitionMap.Values; + + private Dictionary definitionMap = new(); + + [ImportingConstructor] + public DefaultKeyBindingManager([ImportMany] KeyBindingDefinition[] definations) + { + definitionMap = definations.ToDictionary(x => x.ConfigKey, x => x); + + jsonConfigFilePath = Path.GetFullPath("./keybind.json"); + Log.LogInfo($"jsonConfigFilePath: {jsonConfigFilePath}"); + + LoadConfig(); + } + + public void SaveConfig() + { + var json = JsonSerializer.Serialize(new Config() { KeyBindings = definitionMap.ToDictionary(x => x.Key, x => KeyBindingDefinition.FormatToExpression(x.Value.Key, x.Value.Modifiers)) }); + File.WriteAllText(jsonConfigFilePath, json); + + Log.LogInfo($"Saved."); + } + + public void LoadConfig() + { + if (File.Exists(jsonConfigFilePath)) + { + try + { + var json = File.ReadAllText(jsonConfigFilePath); + var strMap = JsonSerializer.Deserialize(json).KeyBindings; + + foreach (var item in strMap) + { + var name = item.Key; + var expr = item.Value; + + if (!KeyBindingDefinition.TryParseExpression(expr, out var k, out var m)) + { + Log.LogError($"Can't parse {name} keybinding expr: {expr}"); + continue; + } + + if (definitionMap.TryGetValue(name, out var definition)) + { + definition.Key = k; + definition.Modifiers = m; + } + } + } + catch (Exception e) + { + Log.LogInfo($"Load failed: {e.Message}"); + } + } + + Log.LogInfo($"Loaded."); + } + + public bool CheckKeyBinding(KeyBindingDefinition defination, KeyEventArgs e) + { + var key = (e.Key == Key.System) ? e.SystemKey : e.Key; + + if (defination.Key == Key.None) + return false; + + var modifier = Keyboard.Modifiers; +#if DEBUG + var str = $"{KeyBindingDefinition.FormatToExpression(key, modifier)} check {defination.Name}({KeyBindingDefinition.FormatToExpression(defination)})"; + if (QueryKeyBinding(key, modifier) is KeyBindingDefinition query) + str += $", query {query.Name}({KeyBindingDefinition.FormatToExpression(query)})"; + Log.LogDebug(str); +#endif + return (key == defination.Key) && (modifier == GetActualModifiers(e.Key, defination.Modifiers)); + } + + private static ModifierKeys GetActualModifiers(Key key, ModifierKeys modifiers) + { + switch (key) + { + case Key.LeftCtrl: + case Key.RightCtrl: + modifiers |= ModifierKeys.Control; + return modifiers; + + case Key.LeftAlt: + case Key.RightAlt: + modifiers |= ModifierKeys.Alt; + return modifiers; + + case Key.LeftShift: + case Key.RightShift: + modifiers |= ModifierKeys.Shift; + break; + } + + return modifiers; + } + + public void ChangeKeyBinding(KeyBindingDefinition definition, Key newKey, ModifierKeys newModifier) + { + Log.LogInfo($"{KeyBindingDefinition.FormatToExpression(definition.Key, definition.Modifiers)} --> {KeyBindingDefinition.FormatToExpression(newKey, newModifier)}"); + + definition.Key = newKey; + definition.Modifiers = newModifier; + } + + public KeyBindingDefinition QueryKeyBinding(Key key, ModifierKeys modifier) + { + if (key is Key.None) + return default; + + return KeyBindingDefinations.FirstOrDefault(x => x.Key == key && modifier == x.Modifiers); + } + } +} diff --git a/OngekiFumenEditor/Kernel/KeyBinding/IKeyBindingManager.cs b/OngekiFumenEditor/Kernel/KeyBinding/IKeyBindingManager.cs new file mode 100644 index 00000000..0e4bf3ed --- /dev/null +++ b/OngekiFumenEditor/Kernel/KeyBinding/IKeyBindingManager.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace OngekiFumenEditor.Kernel.KeyBinding +{ + internal interface IKeyBindingManager + { + bool CheckKeyBinding(KeyBindingDefinition defination, KeyEventArgs e); + + void ChangeKeyBinding(KeyBindingDefinition definition, Key newKey, ModifierKeys newModifier); + + void DefaultKeyBinding(KeyBindingDefinition definition) => + ChangeKeyBinding(definition, definition.DefaultKey, definition.DefaultModifiers); + + KeyBindingDefinition QueryKeyBinding(Key key, ModifierKeys modifier); + + void SaveConfig(); + + void LoadConfig(); + + IEnumerable KeyBindingDefinations { get; } + } +} diff --git a/OngekiFumenEditor/Kernel/KeyBinding/KeyBindingDefinition.cs b/OngekiFumenEditor/Kernel/KeyBinding/KeyBindingDefinition.cs new file mode 100644 index 00000000..ddad9050 --- /dev/null +++ b/OngekiFumenEditor/Kernel/KeyBinding/KeyBindingDefinition.cs @@ -0,0 +1,117 @@ +using Caliburn.Micro; +using OngekiFumenEditor.Properties; +using System.Text.RegularExpressions; +using System; +using System.Windows.Input; +using Xceed.Wpf.Toolkit.Core.Input; + +namespace OngekiFumenEditor.Kernel.KeyBinding +{ + public class KeyBindingDefinition : PropertyChangedBase + { + private readonly string resourceName; + + public Key DefaultKey { get; } + public ModifierKeys DefaultModifiers { get; } + + public string ConfigKey => resourceName; + + public string Name => resourceName/*Resources.ResourceManager.GetString(resourceName)*/; + + public KeyBindingDefinition(string resourceName, Key defaultKey) : this(resourceName, ModifierKeys.None, defaultKey) + { + + } + + public KeyBindingDefinition(string resourceName, ModifierKeys defaultModifiers, Key defaultKey) + { + this.resourceName = resourceName; + + DefaultModifiers = defaultModifiers; + DefaultKey = defaultKey; + } + + private Key? key; + public Key Key + { + get => key ?? DefaultKey; + set + { + Set(ref key, value); + } + } + + private ModifierKeys? modifiers; + public ModifierKeys Modifiers + { + get => modifiers ?? DefaultModifiers; + set + { + Set(ref modifiers, value); + } + } + + public static string FormatToExpression(Key key, ModifierKeys modifier) + { + var modifierStr = modifier switch + { + ModifierKeys.Alt => "Alt", + ModifierKeys.Control => "Ctrl", + ModifierKeys.Shift => "Shift", + ModifierKeys.Windows => "Win", + _ => string.Empty, + }; + + var expr = key is Key.None ? string.Empty : key.ToString(); + + if (!string.IsNullOrWhiteSpace(modifierStr)) + expr = modifierStr + " + " + expr; + + return expr; + } + + public static string FormatToExpression(KeyBindingDefinition definition) + { + return FormatToExpression(definition.Key, definition.Modifiers); + } + + //Ctrl + A + static Regex regex = new Regex(@"(\s*\w+\s*\+\s*)?(\w+)"); + + public static bool TryParseExpression(string keybindExpr, out Key key, out ModifierKeys modifier) + { + key = Key.None; + modifier = ModifierKeys.None; + + if (string.IsNullOrWhiteSpace(keybindExpr)) + return true; + + var match = regex.Match(keybindExpr); + if (!match.Success) + return false; + + var modifierStr = match.Groups[1].Value.Trim().ToLower().TrimEnd('+').Trim(); + if (!string.IsNullOrWhiteSpace(modifierStr)) + { + modifier = modifierStr switch + { + "ctrl" or "control" => ModifierKeys.Control, + "win" or "windows" => ModifierKeys.Windows, + "alt" => ModifierKeys.Alt, + "shift" => ModifierKeys.Shift, + _ => ModifierKeys.None + }; + + if (modifier == ModifierKeys.None) + return false; + } + + var keyStr = match.Groups[2].Value.Trim(); + if (!Enum.TryParse(keyStr, true, out var k)) + return false; + + key = k; + return key != Key.None; + } + } +} \ No newline at end of file diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml new file mode 100644 index 00000000..77711955 --- /dev/null +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml.cs b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml.cs new file mode 100644 index 00000000..dc209feb --- /dev/null +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml.cs @@ -0,0 +1,144 @@ +using Caliburn.Micro; +using MahApps.Metro.Controls; +using OngekiFumenEditor.Kernel.KeyBinding; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using static Xv2CoreLib.PtrWriter; + +namespace OngekiFumenEditor.Kernel.SettingPages.KeyBinding.Dialogs +{ + /// + /// ConfigKeyBindingDialog.xaml 的交互逻辑 + /// + public partial class ConfigKeyBindingDialog : MetroWindow, INotifyPropertyChanged + { + public KeyBindingDefinition Definition { get; } + + private ModifierKeys modifier; + private Key key; + + public event PropertyChangedEventHandler PropertyChanged; + + public string CurrentExpression => KeyBindingDefinition.FormatToExpression(key, modifier); + + public KeyBindingDefinition ConflictDefinition { get; private set; } + + public ConfigKeyBindingDialog(KeyBindingDefinition definition) + { + Definition = definition; + key = definition.Key; + modifier = definition.Modifiers; + + InitializeComponent(); + + KeyDown += OnKeyDown; + KeyUp += OnKeyUp; + } + + private void OnKeyUp(object sender, KeyEventArgs e) + { + if (!IsActive) + return; + + var key = (e.Key == Key.System ? e.SystemKey : e.Key); + + if (TryGetModifier(key, out var modifier)) + this.modifier = modifier; + + UpdateExpression(); + } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + if (!IsActive) + return; + + var key = (e.Key == Key.System ? e.SystemKey : e.Key); + + if (TryGetModifier(key, out var modifier)) + this.modifier = modifier; + else + this.key = key; + + UpdateExpression(); + } + + private bool TryGetModifier(Key key, out ModifierKeys modifier) + { + switch (key) + { + case Key.LeftCtrl or Key.RightCtrl: + modifier = ModifierKeys.Control; + return true; + case Key.LeftShift or Key.RightShift: + modifier = ModifierKeys.Shift; + return true; + case Key.LeftAlt or Key.RightAlt: + modifier = ModifierKeys.Alt; + return true; + case Key.LWin or Key.RWin: + modifier = ModifierKeys.Windows; + return true; + default: + modifier = ModifierKeys.None; + return false; + } + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + //重新设置 + key = Key.None; + modifier = ModifierKeys.None; + + UpdateExpression(); + } + + private void UpdateExpression() + { + PropertyChanged?.Invoke(this, new(nameof(CurrentExpression))); + + if (!string.IsNullOrWhiteSpace(CurrentExpression)) + { + ConflictDefinition = IoC.Get().QueryKeyBinding(key, modifier); + PropertyChanged?.Invoke(this, new(nameof(ConflictDefinition))); + } + } + + private void Button_Click_1(object sender, RoutedEventArgs e) + { + //确定 + if (ConflictDefinition is not null) + { + if (MessageBox.Show($"你绑定的键位和 {ConflictDefinition.Name} 冲突, 如果继续绑定则清空对方冲突的键位, 是否继续?", "警告", MessageBoxButton.YesNo) != MessageBoxResult.Yes) + return; + } + UpdateExpression(); + DialogResult = true; + Close(); + } + + private void Button_Click_2(object sender, RoutedEventArgs e) + { + //清空绑定 + key = Key.None; + modifier = ModifierKeys.None; + + UpdateExpression(); + DialogResult = true; + Close(); + } + } +} diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Models/KeyBindingDefinitionWrapper.cs b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Models/KeyBindingDefinitionWrapper.cs new file mode 100644 index 00000000..c9ecebc9 --- /dev/null +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Models/KeyBindingDefinitionWrapper.cs @@ -0,0 +1,26 @@ +using Caliburn.Micro; +using OngekiFumenEditor.Kernel.KeyBinding; +using OngekiFumenEditor.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows.Media; + +namespace OngekiFumenEditor.Kernel.SettingPages.FumenVisualEditor.Models +{ + public class KeyBindingDefinitionWrapper : PropertyChangedBase + { + private readonly KeyBindingDefinition definition; + + public KeyBindingDefinitionWrapper(KeyBindingDefinition definition) + { + this.definition = definition; + } + + public KeyBindingDefinition Definition => definition; + } +} diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ValueConverters/ShowKeybindExpressionValueConverter.cs b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ValueConverters/ShowKeybindExpressionValueConverter.cs new file mode 100644 index 00000000..e2b91190 --- /dev/null +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ValueConverters/ShowKeybindExpressionValueConverter.cs @@ -0,0 +1,28 @@ +using OngekiFumenEditor.Kernel.KeyBinding; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using System.Windows.Input; + +namespace OngekiFumenEditor.Kernel.SettingPages.KeyBinding.ValueConverters +{ + public class ShowKeybindExpressionValueConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.FirstOrDefault() is Key key && values.LastOrDefault() is ModifierKeys modifier) + return KeyBindingDefinition.FormatToExpression(key, modifier); + + return string.Empty; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs new file mode 100644 index 00000000..287f907a --- /dev/null +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs @@ -0,0 +1,51 @@ +using Caliburn.Micro; +using Gemini.Modules.Settings; +using OngekiFumenEditor.Kernel.KeyBinding; +using OngekiFumenEditor.Kernel.SettingPages.FumenVisualEditor.Models; +using OngekiFumenEditor.Kernel.SettingPages.KeyBinding.Dialogs; +using OngekiFumenEditor.Properties; +using OngekiFumenEditor.UI.Dialogs; +using OngekiFumenEditor.Utils; +using System.ComponentModel.Composition; +using System.Linq; + +namespace OngekiFumenEditor.Kernel.SettingPages.KeyBinding.ViewModels +{ + [Export(typeof(ISettingsEditor))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class KeyBindingSettingViewModel : PropertyChangedBase, ISettingsEditor + { + private readonly IKeyBindingManager keybindingManager; + + public KeyBindingSettingViewModel() + { + keybindingManager = IoC.Get(); + + Definitions = keybindingManager.KeyBindingDefinations.ToArray(); + } + + public string SettingsPagePath => "快捷键"; + + public string SettingsPageName => "键位设置"; + + public KeyBindingDefinition[] Definitions { get; } + + public void ApplyChanges() + { + keybindingManager.SaveConfig(); + } + + public void ChangeKeybind(ActionExecutionContext ctx) + { + if (ctx.Source.DataContext is not KeyBindingDefinition definition) + return; + + var dialog = new ConfigKeyBindingDialog(definition); + if (dialog.ShowDialog() is true) + { + if (KeyBindingDefinition.TryParseExpression(dialog.CurrentExpression, out var newKey, out var newModifier)) + keybindingManager.ChangeKeyBinding(definition, newKey, newModifier); + } + } + } +} diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml new file mode 100644 index 00000000..7094f16a --- /dev/null +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml.cs b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml.cs index dc209feb..bbe7bbe1 100644 --- a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml.cs +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Dialogs/ConfigKeyBindingDialog.xaml.cs @@ -113,13 +113,14 @@ private void UpdateExpression() if (!string.IsNullOrWhiteSpace(CurrentExpression)) { ConflictDefinition = IoC.Get().QueryKeyBinding(key, modifier); + if (ConflictDefinition == Definition) + ConflictDefinition = default; PropertyChanged?.Invoke(this, new(nameof(ConflictDefinition))); } } private void Button_Click_1(object sender, RoutedEventArgs e) { - //确定 if (ConflictDefinition is not null) { if (MessageBox.Show($"你绑定的键位和 {ConflictDefinition.Name} 冲突, 如果继续绑定则清空对方冲突的键位, 是否继续?", "警告", MessageBoxButton.YesNo) != MessageBoxResult.Yes) @@ -132,13 +133,9 @@ private void Button_Click_1(object sender, RoutedEventArgs e) private void Button_Click_2(object sender, RoutedEventArgs e) { - //清空绑定 - key = Key.None; - modifier = ModifierKeys.None; - + key = Definition.DefaultKey; + modifier = Definition.DefaultModifiers; UpdateExpression(); - DialogResult = true; - Close(); } } } diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs index 287f907a..9f9fc296 100644 --- a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs @@ -6,8 +6,12 @@ using OngekiFumenEditor.Properties; using OngekiFumenEditor.UI.Dialogs; using OngekiFumenEditor.Utils; +using System; +using System.Collections.ObjectModel; using System.ComponentModel.Composition; using System.Linq; +using System.Windows; +using System.Windows.Input; namespace OngekiFumenEditor.Kernel.SettingPages.KeyBinding.ViewModels { @@ -21,14 +25,50 @@ public KeyBindingSettingViewModel() { keybindingManager = IoC.Get(); - Definitions = keybindingManager.KeyBindingDefinations.ToArray(); + definitions = keybindingManager.KeyBindingDefinations.ToArray(); + UpdateDisplayList(); + } + + private void UpdateDisplayList() + { + Definitions.Clear(); + var list = definitions.AsEnumerable(); + + if (IsShowNotAssignOnly) + list = list.Where(x => x.Key == Key.None); + + if (!string.IsNullOrWhiteSpace(FilterKeywords)) + list = list.Where(x => string.Join(" ", [ + x.Name, + x.Key, + x.Modifiers, + x.ConfigKey + ]).Contains(FilterKeywords, StringComparison.InvariantCultureIgnoreCase)); + + Definitions.AddRange(list); } public string SettingsPagePath => "快捷键"; public string SettingsPageName => "键位设置"; - public KeyBindingDefinition[] Definitions { get; } + private KeyBindingDefinition[] definitions; + + public ObservableCollection Definitions { get; } = new(); + + private bool isShowNotAssignOnly; + public bool IsShowNotAssignOnly + { + get => isShowNotAssignOnly; + set => Set(ref isShowNotAssignOnly, value); + } + + private string filterKeywords; + public string FilterKeywords + { + get => filterKeywords; + set => Set(ref filterKeywords, value); + } public void ApplyChanges() { @@ -46,6 +86,15 @@ public void ChangeKeybind(ActionExecutionContext ctx) if (KeyBindingDefinition.TryParseExpression(dialog.CurrentExpression, out var newKey, out var newModifier)) keybindingManager.ChangeKeyBinding(definition, newKey, newModifier); } + if (dialog.ConflictDefinition is KeyBindingDefinition conflictDefinition) + keybindingManager.ChangeKeyBinding(conflictDefinition, Key.None, ModifierKeys.None); + } + public void ResetAllDefinitions() + { + if (MessageBox.Show(Resources.ComfirmResetAllKeybindingDefinitions, Resources.Warning, MessageBoxButton.YesNo) != MessageBoxResult.Yes) + return; + foreach (var definition in Definitions) + keybindingManager.DefaultKeyBinding(definition); } } } diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml index 7094f16a..68ae04e1 100644 --- a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml @@ -48,7 +48,7 @@ Margin="30,3,3,3" Padding="10,5" cal:Message.Attach="ChangeKeybind($executionContext)" - Content="更改绑定" /> + Content="{markup:Translate [Change]}" /> diff --git a/OngekiFumenEditor/Properties/Resources.Designer.cs b/OngekiFumenEditor/Properties/Resources.Designer.cs index 59156761..f2710762 100644 --- a/OngekiFumenEditor/Properties/Resources.Designer.cs +++ b/OngekiFumenEditor/Properties/Resources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // 此代码由工具生成。 // 运行时版本:4.0.30319.42000 @@ -924,6 +924,15 @@ public static string ComfirmInterpolateMessage { } } + /// + /// 查找类似 Does it reset all key bindings to their default values? 的本地化字符串。 + /// + public static string ComfirmResetAllKeybindingDefinitions { + get { + return ResourceManager.GetString("ComfirmResetAllKeybindingDefinitions", resourceCulture); + } + } + /// /// 查找类似 About 的本地化字符串。 /// @@ -2616,6 +2625,168 @@ public static string JustBefore { } } + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_CancelSelectingObjects { + get { + return ResourceManager.GetString("kbd_editor_CancelSelectingObjects", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_CopySelectedObjects { + get { + return ResourceManager.GetString("kbd_editor_CopySelectedObjects", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_DeleteSelectingObjects { + get { + return ResourceManager.GetString("kbd_editor_DeleteSelectingObjects", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastAddConnectableChild { + get { + return ResourceManager.GetString("kbd_editor_FastAddConnectableChild", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastPlaceDockableObjectToCenter { + get { + return ResourceManager.GetString("kbd_editor_FastPlaceDockableObjectToCenter", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastPlaceDockableObjectToLeft { + get { + return ResourceManager.GetString("kbd_editor_FastPlaceDockableObjectToLeft", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastPlaceDockableObjectToRight { + get { + return ResourceManager.GetString("kbd_editor_FastPlaceDockableObjectToRight", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastPlaceDockableObjectToWallLeft { + get { + return ResourceManager.GetString("kbd_editor_FastPlaceDockableObjectToWallLeft", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastPlaceDockableObjectToWallRight { + get { + return ResourceManager.GetString("kbd_editor_FastPlaceDockableObjectToWallRight", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastPlaceNewHold { + get { + return ResourceManager.GetString("kbd_editor_FastPlaceNewHold", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastPlaceNewTap { + get { + return ResourceManager.GetString("kbd_editor_FastPlaceNewTap", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastSetObjectIsCritical { + get { + return ResourceManager.GetString("kbd_editor_FastSetObjectIsCritical", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_FastSwitchFlickDirection { + get { + return ResourceManager.GetString("kbd_editor_FastSwitchFlickDirection", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_HideOrShow { + get { + return ResourceManager.GetString("kbd_editor_HideOrShow", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_PasteCopiesObjects { + get { + return ResourceManager.GetString("kbd_editor_PasteCopiesObjects", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_ScrollPageDown { + get { + return ResourceManager.GetString("kbd_editor_ScrollPageDown", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_ScrollPageUp { + get { + return ResourceManager.GetString("kbd_editor_ScrollPageUp", resourceCulture); + } + } + + /// + /// 查找类似 的本地化字符串。 + /// + public static string kbd_editor_SelectAllObjects { + get { + return ResourceManager.GetString("kbd_editor_SelectAllObjects", resourceCulture); + } + } + /// /// 查找类似 Keywords sorting: 的本地化字符串。 /// diff --git a/OngekiFumenEditor/Properties/Resources.resx b/OngekiFumenEditor/Properties/Resources.resx index e3be9537..f270142b 100644 --- a/OngekiFumenEditor/Properties/Resources.resx +++ b/OngekiFumenEditor/Properties/Resources.resx @@ -1737,4 +1737,80 @@ About + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Does it reset all key bindings to their default values? + + + Change + + + Conflicts with keybinding {0} + + + Please assign keybinding for {0} + + + Reset + + + Clear + + + Assign keybinding + \ No newline at end of file diff --git a/OngekiFumenEditor/Properties/Resources.zh-Hans.resx b/OngekiFumenEditor/Properties/Resources.zh-Hans.resx index cc56b5e6..1802613e 100644 --- a/OngekiFumenEditor/Properties/Resources.zh-Hans.resx +++ b/OngekiFumenEditor/Properties/Resources.zh-Hans.resx @@ -577,7 +577,7 @@ (值越大,滚动跨度越大) - 请先将编辑器切换到设计模式 + 请先将编辑器切换到编辑模式 编辑器目标 @@ -1213,7 +1213,7 @@ 记忆/恢复编辑器时间 - 记录操作位置,设计模式中撤回时如果不在位置内则跳转此位置 + 记录操作位置,编辑模式中撤回时如果不在位置内则跳转此位置 渲染器 @@ -1737,4 +1737,79 @@ 关于 + + 快速调整已选的物件Critical属性 + + + 快速放置依附物件到WallLeft轨道上 + + + 快速放置依附物件到WallRight轨道上 + + + 快速放置依附物件到Right轨道上 + + + 快速放置新的Hold物件 + + + 快速放置新的Tap物件 + + + 快速放置依附物件到Center轨道上 + + + 快速放置依附物件到Left轨道上 + + + 删除已选物件 + + + 选取所有物件 + + + 取消选择已选物件 + + + 切换到预览模式或编辑模式 + + + 快速放置轨道子节点 + + + 快速变更Flick物件的方向 + + + 复制(可复制的)已选物件 + + + 粘贴已被复制的物件 + + + 编辑器向下翻页 + + + 编辑器向上翻页 + + + 是否将所有按键绑定都重置到默认值? + + + 变更 + + + 与 {0} 的绑定键位起冲突 + + + 请为 {0} 输入合适的快捷键组合 + + + 重置 + + + 清除 + + + 设置键位 + \ No newline at end of file diff --git a/OngekiFumenEditor/UI/Markup/TranslateExtension.cs b/OngekiFumenEditor/UI/Markup/TranslateExtension.cs index e2488250..57b2ecfb 100644 --- a/OngekiFumenEditor/UI/Markup/TranslateExtension.cs +++ b/OngekiFumenEditor/UI/Markup/TranslateExtension.cs @@ -1,17 +1,27 @@ -using Gemini.Framework.Markup; +using Caliburn.Micro; +using Gemini.Framework.Languages; +using Gemini.Framework.Markup; using OngekiFumenEditor.Properties; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Data; namespace OngekiFumenEditor.UI.Markup { - public class TranslateExtension : TranslateExtensionBase - { - public TranslateExtension(string member) : base(member, Resources.ResourceManager.GetString) - { - } - } + public class TranslateExtension : TranslateExtensionBase + { + public TranslateExtension() : this(default) //Pass default value at first, and then we assign actual resource name to Path + { + + } + + public TranslateExtension(string resourceName) : base(resourceName, Resources.ResourceManager.GetString) + { + + } + } } diff --git a/OngekiFumenEditor/UI/ValueConverters/LocalizeConverter.cs b/OngekiFumenEditor/UI/ValueConverters/LocalizeConverter.cs new file mode 100644 index 00000000..f80efe5a --- /dev/null +++ b/OngekiFumenEditor/UI/ValueConverters/LocalizeConverter.cs @@ -0,0 +1,29 @@ +using OngekiFumenEditor.Utils; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace OngekiFumenEditor.UI.ValueConverters +{ + public class LocalizeConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Length < 2) + throw new ArgumentException("LocalizeConverter requires >=2 values"); + + var strArr = values.Select(x => x.ToString()).ToArray(); + + return strArr[0].Format(strArr[1..]); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} From 94d33d30449190839bcbe70b2b3b6b2c23207ec6 Mon Sep 17 00:00:00 2001 From: MikiraSora Date: Mon, 21 Oct 2024 13:24:11 +0800 Subject: [PATCH 21/67] add CN I18N --- .../ViewModels/KeyBindingSettingViewModel.cs | 8 +- .../Views/KeyBindingSettingView.xaml | 69 ++++++++++------ OngekiFumenEditor/OngekiFumenEditor.csproj | 8 +- .../Properties/Resources.Designer.cs | 81 +++++++++++++++++++ OngekiFumenEditor/Properties/Resources.resx | 12 ++- .../Properties/Resources.zh-Hans.resx | 9 +++ 6 files changed, 154 insertions(+), 33 deletions(-) diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs index 9f9fc296..0b349eee 100644 --- a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/ViewModels/KeyBindingSettingViewModel.cs @@ -29,7 +29,7 @@ public KeyBindingSettingViewModel() UpdateDisplayList(); } - private void UpdateDisplayList() + public void UpdateDisplayList() { Definitions.Clear(); var list = definitions.AsEnumerable(); @@ -48,9 +48,9 @@ private void UpdateDisplayList() Definitions.AddRange(list); } - public string SettingsPagePath => "快捷键"; + public string SettingsPagePath => Resources.TabDocument; - public string SettingsPageName => "键位设置"; + public string SettingsPageName => Resources.KeyMap; private KeyBindingDefinition[] definitions; @@ -88,6 +88,7 @@ public void ChangeKeybind(ActionExecutionContext ctx) } if (dialog.ConflictDefinition is KeyBindingDefinition conflictDefinition) keybindingManager.ChangeKeyBinding(conflictDefinition, Key.None, ModifierKeys.None); + UpdateDisplayList(); } public void ResetAllDefinitions() { @@ -95,6 +96,7 @@ public void ResetAllDefinitions() return; foreach (var definition in Definitions) keybindingManager.DefaultKeyBinding(definition); + UpdateDisplayList(); } } } diff --git a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml index 68ae04e1..3ee98830 100644 --- a/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml +++ b/OngekiFumenEditor/Kernel/SettingPages/KeyBinding/Views/KeyBindingSettingView.xaml @@ -17,40 +17,61 @@ - - + + + + + + + + + + + +