DocumentManagerViewModel.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. using System.Collections.ObjectModel;
  2. using System.Threading.Tasks;
  3. using Avalonia.Input;
  4. using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
  5. using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Evaluators;
  6. using PixiEditor.AvaloniaUI.Models.Dialogs;
  7. using PixiEditor.AvaloniaUI.Models.Handlers;
  8. using PixiEditor.AvaloniaUI.ViewModels.SubViewModels;
  9. using PixiEditor.AvaloniaUI.ViewModels.Tools.Tools;
  10. using PixiEditor.AvaloniaUI.Views;
  11. using PixiEditor.AvaloniaUI.Views.Overlays.SymmetryOverlay;
  12. using PixiEditor.ChangeableDocument.Enums;
  13. using PixiEditor.UI.Common.Fonts;
  14. namespace PixiEditor.AvaloniaUI.ViewModels.Document;
  15. #nullable enable
  16. [Command.Group("PixiEditor.Document", "IMAGE")]
  17. internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocumentManagerHandler
  18. {
  19. public ObservableCollection<DocumentViewModel> Documents { get; } = new ObservableCollection<DocumentViewModel>();
  20. public event EventHandler<DocumentChangedEventArgs>? ActiveDocumentChanged;
  21. private DocumentViewModel? activeDocument;
  22. public DocumentViewModel? ActiveDocument
  23. {
  24. get => activeDocument;
  25. // Use WindowSubViewModel.MakeDocumentViewportActive(document);
  26. private set
  27. {
  28. if (activeDocument == value)
  29. return;
  30. DocumentViewModel? prevDoc = activeDocument;
  31. activeDocument = value;
  32. OnPropertyChanged(nameof(ActiveDocument));
  33. ActiveDocumentChanged?.Invoke(this, new(value, prevDoc));
  34. if (ViewModelMain.Current.ToolsSubViewModel.ActiveTool == null)
  35. {
  36. ViewModelMain.Current.ToolsSubViewModel.SetActiveTool<PenToolViewModel>(false);
  37. }
  38. }
  39. }
  40. IDocument? IDocumentManagerHandler.ActiveDocument
  41. {
  42. get => ActiveDocument;
  43. set => ActiveDocument = (DocumentViewModel)value;
  44. }
  45. public bool HasActiveDocument => ActiveDocument != null;
  46. public DocumentManagerViewModel(ViewModelMain owner) : base(owner)
  47. {
  48. owner.WindowSubViewModel.ActiveViewportChanged += (_, args) =>
  49. {
  50. ActiveDocument = args.Document;
  51. };
  52. }
  53. public void MakeActiveDocumentNull() => ActiveDocument = null;
  54. [Evaluator.CanExecute("PixiEditor.HasDocument", nameof(ActiveDocument))]
  55. public bool DocumentNotNull() => ActiveDocument != null;
  56. [Command.Basic("PixiEditor.Document.ClipCanvas", "CLIP_CANVAS", "CLIP_CANVAS", CanExecute = "PixiEditor.HasDocument",
  57. Icon = PixiPerfectIcons.Crop, MenuItemPath = "IMAGE/CLIP_CANVAS", MenuItemOrder = 2)]
  58. public void ClipCanvas() => ActiveDocument?.Operations.ClipCanvas();
  59. [Command.Basic("PixiEditor.Document.FlipImageHorizontal", FlipType.Horizontal, "FLIP_IMG_HORIZONTALLY", "FLIP_IMG_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
  60. MenuItemPath = "IMAGE/FLIP/FLIP_IMG_HORIZONTALLY", MenuItemOrder = 14, Icon = PixiPerfectIcons.XFlip)]
  61. [Command.Basic("PixiEditor.Document.FlipImageVertical", FlipType.Vertical, "FLIP_IMG_VERTICALLY", "FLIP_IMG_VERTICALLY", CanExecute = "PixiEditor.HasDocument",
  62. MenuItemPath = "IMAGE/FLIP/FLIP_IMG_VERTICALLY", MenuItemOrder = 15, Icon = PixiPerfectIcons.YFlip)]
  63. public void FlipImage(FlipType type) => ActiveDocument?.Operations.FlipImage(type, activeDocument.AnimationDataViewModel.ActiveFrameBindable);
  64. [Command.Basic("PixiEditor.Document.FlipLayersHorizontal", FlipType.Horizontal, "FLIP_LAYERS_HORIZONTALLY", "FLIP_LAYERS_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
  65. MenuItemPath = "IMAGE/FLIP/FLIP_LAYERS_HORIZONTALLY", MenuItemOrder = 16, Icon = PixiPerfectIcons.XSelectedFlip)]
  66. [Command.Basic("PixiEditor.Document.FlipLayersVertical", FlipType.Vertical, "FLIP_LAYERS_VERTICALLY", "FLIP_LAYERS_VERTICALLY", CanExecute = "PixiEditor.HasDocument",
  67. MenuItemPath = "IMAGE/FLIP/FLIP_LAYERS_VERTICALLY", MenuItemOrder = 17, Icon = PixiPerfectIcons.YSelectedFlip)]
  68. public void FlipLayers(FlipType type)
  69. {
  70. if (ActiveDocument?.SelectedStructureMember == null)
  71. return;
  72. ActiveDocument?.Operations.FlipImage(type, ActiveDocument.GetSelectedMembers(), activeDocument.AnimationDataViewModel.ActiveFrameBindable);
  73. }
  74. [Command.Basic("PixiEditor.Document.Rotate90Deg", "ROT_IMG_90",
  75. "ROT_IMG_90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D90,
  76. MenuItemPath = "IMAGE/ROTATION/ROT_IMG_90_D", MenuItemOrder = 8, Icon = PixiPerfectIcons.RotateImage90)]
  77. [Command.Basic("PixiEditor.Document.Rotate180Deg", "ROT_IMG_180",
  78. "ROT_IMG_180", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D180,
  79. MenuItemPath = "IMAGE/ROTATION/ROT_IMG_180_D", MenuItemOrder = 9, Icon = PixiPerfectIcons.RotateImage180)]
  80. [Command.Basic("PixiEditor.Document.Rotate270Deg", "ROT_IMG_-90",
  81. "ROT_IMG_-90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D270,
  82. MenuItemPath = "IMAGE/ROTATION/ROT_IMG_-90_D", MenuItemOrder = 10, Icon = PixiPerfectIcons.RotateImageMinus90)]
  83. public void RotateImage(RotationAngle angle) => ActiveDocument?.Operations.RotateImage(angle);
  84. [Command.Basic("PixiEditor.Document.Rotate90DegLayers", "ROT_LAYERS_90",
  85. "ROT_LAYERS_90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D90,
  86. MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_90_D", MenuItemOrder = 11, Icon = PixiPerfectIcons.RotateFile90)]
  87. [Command.Basic("PixiEditor.Document.Rotate180DegLayers", "ROT_LAYERS_180",
  88. "ROT_LAYERS_180", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D180,
  89. MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_180_D", MenuItemOrder = 12, Icon = PixiPerfectIcons.RotateFile180)]
  90. [Command.Basic("PixiEditor.Document.Rotate270DegLayers", "ROT_LAYERS_-90",
  91. "ROT_LAYERS_-90", CanExecute = "PixiEditor.HasDocument", Parameter = RotationAngle.D270,
  92. MenuItemPath = "IMAGE/ROTATION/ROT_LAYERS_-90_D", MenuItemOrder = 13, Icon = PixiPerfectIcons.RotateFileMinus90)]
  93. public void RotateLayers(RotationAngle angle)
  94. {
  95. if (ActiveDocument?.SelectedStructureMember == null)
  96. return;
  97. ActiveDocument?.Operations.RotateImage(angle, ActiveDocument.GetSelectedMembers(), activeDocument.AnimationDataViewModel.ActiveFrameBindable);
  98. }
  99. [Command.Basic("PixiEditor.Document.ToggleVerticalSymmetryAxis", "TOGGLE_VERT_SYMMETRY_AXIS", "TOGGLE_VERT_SYMMETRY_AXIS", CanExecute = "PixiEditor.HasDocument",
  100. Icon = PixiPerfectIcons.YSymmetry)]
  101. public void ToggleVerticalSymmetryAxis()
  102. {
  103. if (ActiveDocument is null)
  104. return;
  105. ActiveDocument.VerticalSymmetryAxisEnabledBindable ^= true;
  106. }
  107. [Command.Basic("PixiEditor.Document.ToggleHorizontalSymmetryAxis", "TOGGLE_HOR_SYMMETRY_AXIS", "TOGGLE_HOR_SYMMETRY_AXIS", CanExecute = "PixiEditor.HasDocument",
  108. Icon = PixiPerfectIcons.XSymmetry)]
  109. public void ToggleHorizontalSymmetryAxis()
  110. {
  111. if (ActiveDocument is null)
  112. return;
  113. ActiveDocument.HorizontalSymmetryAxisEnabledBindable ^= true;
  114. }
  115. [Command.Internal("PixiEditor.Document.DragSymmetry", CanExecute = "PixiEditor.HasDocument")]
  116. public void DragSymmetry(SymmetryAxisDragInfo info)
  117. {
  118. if (ActiveDocument is null)
  119. return;
  120. ActiveDocument.EventInlet.OnSymmetryDragged(info);
  121. }
  122. [Command.Internal("PixiEditor.Document.StartDragSymmetry", CanExecute = "PixiEditor.HasDocument")]
  123. public void StartDragSymmetry(SymmetryAxisDirection dir)
  124. {
  125. if (ActiveDocument is null)
  126. return;
  127. ActiveDocument.EventInlet.OnSymmetryDragStarted(dir);
  128. ActiveDocument.Tools.UseSymmetry(dir);
  129. }
  130. [Command.Internal("PixiEditor.Document.EndDragSymmetry", CanExecute = "PixiEditor.HasDocument")]
  131. public void EndDragSymmetry(SymmetryAxisDirection dir)
  132. {
  133. if (ActiveDocument is null)
  134. return;
  135. ActiveDocument.EventInlet.OnSymmetryDragEnded(dir);
  136. }
  137. [Command.Basic("PixiEditor.Document.DeletePixels", "DELETE_PIXELS", "DELETE_PIXELS_DESCRIPTIVE",
  138. CanExecute = "PixiEditor.Selection.IsNotEmpty", Key = Key.Delete,
  139. ShortcutContext = typeof(ViewportWindowViewModel),
  140. Icon = PixiPerfectIcons.Eraser,
  141. MenuItemPath = "EDIT/DELETE_SELECTED_PIXELS", MenuItemOrder = 6)]
  142. public void DeletePixels()
  143. {
  144. Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.DeleteSelectedPixels(activeDocument.AnimationDataViewModel.ActiveFrameBindable);
  145. }
  146. [Command.Basic("PixiEditor.Document.ResizeDocument", false, "RESIZE_DOCUMENT", "RESIZE_DOCUMENT", CanExecute = "PixiEditor.HasDocument", Key = Key.I, Modifiers = KeyModifiers.Control | KeyModifiers.Shift,
  147. Icon = PixiPerfectIcons.Resize, MenuItemPath = "IMAGE/RESIZE_IMAGE", MenuItemOrder = 0)]
  148. [Command.Basic("PixiEditor.Document.ResizeCanvas", true, "RESIZE_CANVAS", "RESIZE_CANVAS", CanExecute = "PixiEditor.HasDocument", Key = Key.C, Modifiers = KeyModifiers.Control | KeyModifiers.Shift,
  149. Icon = PixiPerfectIcons.CanvasResize, MenuItemPath = "IMAGE/RESIZE_CANVAS", MenuItemOrder = 1)]
  150. public async Task OpenResizePopup(bool canvas)
  151. {
  152. DocumentViewModel? doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  153. if (doc is null)
  154. return;
  155. ResizeDocumentDialog dialog = new ResizeDocumentDialog(
  156. doc.Width,
  157. doc.Height,
  158. MainWindow.Current!,
  159. canvas);
  160. if (await dialog.ShowDialog())
  161. {
  162. if (canvas)
  163. {
  164. doc.Operations.ResizeCanvas(new(dialog.Width, dialog.Height), dialog.ResizeAnchor);
  165. }
  166. else
  167. {
  168. doc.Operations.ResizeImage(new(dialog.Width, dialog.Height), ResamplingMethod.NearestNeighbor);
  169. }
  170. }
  171. }
  172. [Command.Basic("PixiEditor.Document.CenterContent", "CENTER_CONTENT", "CENTER_CONTENT", CanExecute = "PixiEditor.HasDocument",
  173. Icon = PixiPerfectIcons.Center, MenuItemPath = "IMAGE/CENTER_CONTENT", MenuItemOrder = 3)]
  174. public void CenterContent()
  175. {
  176. if(ActiveDocument?.SelectedStructureMember == null)
  177. return;
  178. ActiveDocument.Operations.CenterContent(ActiveDocument.GetSelectedMembers(), activeDocument.AnimationDataViewModel.ActiveFrameBindable);
  179. }
  180. }