LayersViewModel.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. using System.Collections.Generic;
  2. using System.Collections.Immutable;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using Avalonia;
  6. using Avalonia.Controls.ApplicationLifetimes;
  7. using Avalonia.Input;
  8. using Avalonia.Media;
  9. using Avalonia.Media.Imaging;
  10. using Avalonia.Platform;
  11. using Avalonia.Platform.Storage;
  12. using PixiEditor.AvaloniaUI.Helpers.Converters;
  13. using PixiEditor.AvaloniaUI.Helpers.Extensions;
  14. using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
  15. using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Evaluators;
  16. using PixiEditor.AvaloniaUI.Models.Dialogs;
  17. using PixiEditor.AvaloniaUI.Models.IO;
  18. using PixiEditor.AvaloniaUI.Models.Layers;
  19. using PixiEditor.AvaloniaUI.ViewModels.Dock;
  20. using PixiEditor.AvaloniaUI.ViewModels.Document;
  21. using PixiEditor.ChangeableDocument.Enums;
  22. using PixiEditor.DrawingApi.Core.Numerics;
  23. using PixiEditor.Extensions.Common.Localization;
  24. using PixiEditor.Extensions.Exceptions;
  25. using PixiEditor.Numerics;
  26. using PixiEditor.UI.Common.Fonts;
  27. namespace PixiEditor.AvaloniaUI.ViewModels.SubViewModels;
  28. #nullable enable
  29. [Command.Group("PixiEditor.Layer", "LAYER")]
  30. internal class LayersViewModel : SubViewModel<ViewModelMain>
  31. {
  32. public LayersViewModel(ViewModelMain owner)
  33. : base(owner)
  34. {
  35. }
  36. public void CreateFolderFromActiveLayers()
  37. {
  38. }
  39. public bool CanCreateFolderFromSelected()
  40. {
  41. return false;
  42. }
  43. [Evaluator.CanExecute("PixiEditor.Layer.CanDeleteSelected")]
  44. public bool CanDeleteSelected()
  45. {
  46. var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
  47. if (member is null)
  48. return false;
  49. return true;
  50. }
  51. [Command.Basic("PixiEditor.Layer.DeleteSelected", "LAYER_DELETE_SELECTED", "LAYER_DELETE_SELECTED_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.CanDeleteSelected",
  52. Icon = PixiPerfectIcons.Trash)]
  53. public void DeleteSelected()
  54. {
  55. var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
  56. if (member is null)
  57. return;
  58. member.Document.Operations.DeleteStructureMember(member.GuidValue);
  59. }
  60. [Evaluator.CanExecute("PixiEditor.Layer.HasSelectedMembers")]
  61. public bool HasSelectedMembers()
  62. {
  63. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  64. if (doc is null)
  65. return false;
  66. return doc.SelectedStructureMember is not null || doc.SoftSelectedStructureMembers.Count > 0;
  67. }
  68. [Evaluator.CanExecute("PixiEditor.Layer.HasMultipleSelectedMembers")]
  69. public bool HasMultipleSelectedMembers()
  70. {
  71. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  72. if (doc is null)
  73. return false;
  74. int count = doc.SoftSelectedStructureMembers.Count;
  75. if (doc.SelectedStructureMember is not null)
  76. count++;
  77. return count > 1;
  78. }
  79. private List<Guid> GetSelected()
  80. {
  81. List<Guid> members = new();
  82. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  83. if (doc is null)
  84. return members;
  85. if (doc.SelectedStructureMember is not null)
  86. members.Add(doc.SelectedStructureMember.GuidValue);
  87. members.AddRange(doc.SoftSelectedStructureMembers.Select(static member => member.GuidValue));
  88. return members;
  89. }
  90. [Command.Basic("PixiEditor.Layer.DeleteAllSelected", "LAYER_DELETE_ALL_SELECTED", "LAYER_DELETE_ALL_SELECTED_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasSelectedMembers",
  91. Icon = PixiPerfectIcons.Trash)]
  92. public void DeleteAllSelected()
  93. {
  94. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  95. if (doc is null)
  96. return;
  97. var selected = GetSelected();
  98. if (selected.Count > 0)
  99. doc.Operations.DeleteStructureMembers(selected);
  100. }
  101. [Command.Basic("PixiEditor.Layer.NewFolder", "NEW_FOLDER", "CREATE_NEW_FOLDER", CanExecute = "PixiEditor.Layer.CanCreateNewMember", Icon = "Folder-add.png")]
  102. public void NewFolder()
  103. {
  104. if (Owner.DocumentManagerSubViewModel.ActiveDocument is not { } doc)
  105. return;
  106. doc.Operations.CreateStructureMember(StructureMemberType.Folder);
  107. }
  108. [Command.Basic("PixiEditor.Layer.NewLayer", "NEW_LAYER", "CREATE_NEW_LAYER", CanExecute = "PixiEditor.Layer.CanCreateNewMember", Key = Key.N, Modifiers = KeyModifiers.Control | KeyModifiers.Shift, Icon = "Layer-add.png")]
  109. public void NewLayer()
  110. {
  111. if (Owner.DocumentManagerSubViewModel.ActiveDocument is not { } doc)
  112. return;
  113. doc.Operations.CreateStructureMember(StructureMemberType.Layer);
  114. }
  115. [Evaluator.CanExecute("PixiEditor.Layer.CanCreateNewMember")]
  116. public bool CanCreateNewMember()
  117. {
  118. return Owner.DocumentManagerSubViewModel.ActiveDocument is { UpdateableChangeActive: false };
  119. }
  120. [Command.Internal("PixiEditor.Layer.ToggleLockTransparency", CanExecute = "PixiEditor.Layer.SelectedMemberIsLayer")]
  121. public void ToggleLockTransparency()
  122. {
  123. var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
  124. if (member is not LayerViewModel layerVm)
  125. return;
  126. layerVm.LockTransparencyBindable = !layerVm.LockTransparencyBindable;
  127. }
  128. [Command.Internal("PixiEditor.Layer.OpacitySliderDragStarted")]
  129. public void OpacitySliderDragStarted()
  130. {
  131. Owner.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseOpacitySlider();
  132. Owner.DocumentManagerSubViewModel.ActiveDocument?.EventInlet.OnOpacitySliderDragStarted();
  133. }
  134. [Command.Internal("PixiEditor.Layer.OpacitySliderDragged")]
  135. public void OpacitySliderDragged(double value)
  136. {
  137. Owner.DocumentManagerSubViewModel.ActiveDocument?.EventInlet.OnOpacitySliderDragged((float)value);
  138. }
  139. [Command.Internal("PixiEditor.Layer.OpacitySliderDragEnded")]
  140. public void OpacitySliderDragEnded()
  141. {
  142. Owner.DocumentManagerSubViewModel.ActiveDocument?.EventInlet.OnOpacitySliderDragEnded();
  143. }
  144. [Command.Internal("PixiEditor.Layer.OpacitySliderSet")]
  145. public void OpacitySliderSet(double value)
  146. {
  147. var document = Owner.DocumentManagerSubViewModel.ActiveDocument;
  148. if (document?.SelectedStructureMember != null)
  149. {
  150. document.Operations.SetMemberOpacity(document.SelectedStructureMember.GuidValue, (float)value);
  151. }
  152. }
  153. [Command.Basic("PixiEditor.Layer.DuplicateSelectedLayer", "DUPLICATE_SELECTED_LAYER", "DUPLICATE_SELECTED_LAYER", CanExecute = "PixiEditor.Layer.SelectedMemberIsLayer",
  154. Icon = PixiPerfectIcons.DuplicateFile, MenuItemPath = "EDIT/DUPLICATE", MenuItemOrder = 5)]
  155. public void DuplicateLayer()
  156. {
  157. var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
  158. if (member is not LayerViewModel layerVM)
  159. return;
  160. member.Document.Operations.DuplicateLayer(member.GuidValue);
  161. }
  162. [Evaluator.CanExecute("PixiEditor.Layer.SelectedMemberIsLayer")]
  163. public bool SelectedMemberIsLayer(object property)
  164. {
  165. var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
  166. return member is LayerViewModel;
  167. }
  168. private bool HasSelectedMember(bool above)
  169. {
  170. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  171. var member = doc?.SelectedStructureMember;
  172. if (member is null)
  173. return false;
  174. var path = doc!.StructureHelper.FindPath(member.GuidValue);
  175. if (path.Count < 2)
  176. return false;
  177. var parent = (FolderViewModel)path[1];
  178. int index = parent.Children.IndexOf(path[0]);
  179. if (above && index == parent.Children.Count - 1)
  180. return false;
  181. if (!above && index == 0)
  182. return false;
  183. return true;
  184. }
  185. private void MoveSelectedMember(bool upwards)
  186. {
  187. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  188. var member = Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember;
  189. if (member is null)
  190. return;
  191. var path = doc!.StructureHelper.FindPath(member.GuidValue);
  192. if (path.Count < 2)
  193. return;
  194. var parent = (FolderViewModel)path[1];
  195. int curIndex = parent.Children.IndexOf(path[0]);
  196. if (upwards)
  197. {
  198. if (curIndex == parent.Children.Count - 1)
  199. return;
  200. doc.Operations.MoveStructureMember(member.GuidValue, parent.Children[curIndex + 1].GuidValue, StructureMemberPlacement.Above);
  201. }
  202. else
  203. {
  204. if (curIndex == 0)
  205. return;
  206. doc.Operations.MoveStructureMember(member.GuidValue, parent.Children[curIndex - 1].GuidValue, StructureMemberPlacement.Below);
  207. }
  208. }
  209. [Evaluator.CanExecute("PixiEditor.Layer.ActiveLayerHasMask")]
  210. public bool ActiveMemberHasMask() => Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember?.HasMaskBindable ?? false;
  211. [Evaluator.CanExecute("PixiEditor.Layer.ActiveLayerHasNoMask")]
  212. public bool ActiveLayerHasNoMask() => !Owner.DocumentManagerSubViewModel.ActiveDocument?.SelectedStructureMember?.HasMaskBindable ?? false;
  213. [Command.Basic("PixiEditor.Layer.CreateMask", "CREATE_MASK", "CREATE_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasNoMask", Icon = "Create-mask.png")]
  214. public void CreateMask()
  215. {
  216. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  217. var member = doc?.SelectedStructureMember;
  218. if (member is null || member.HasMaskBindable)
  219. return;
  220. doc!.Operations.CreateMask(member);
  221. }
  222. [Command.Basic("PixiEditor.Layer.DeleteMask", "DELETE_MASK", "DELETE_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask", Icon = "PixiPerfectIcons.Trash")]
  223. public void DeleteMask()
  224. {
  225. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  226. var member = doc?.SelectedStructureMember;
  227. if (member is null || !member.HasMaskBindable)
  228. return;
  229. doc!.Operations.DeleteMask(member);
  230. }
  231. [Command.Basic("PixiEditor.Layer.ToggleMask", "TOGGLE_MASK", "TOGGLE_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask")]
  232. public void ToggleMask()
  233. {
  234. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  235. var member = doc?.SelectedStructureMember;
  236. if (member is null || !member.HasMaskBindable)
  237. return;
  238. member.MaskIsVisibleBindable = !member.MaskIsVisibleBindable;
  239. }
  240. [Command.Basic("PixiEditor.Layer.ApplyMask", "APPLY_MASK", "APPLY_MASK", CanExecute = "PixiEditor.Layer.ActiveLayerHasMask")]
  241. public void ApplyMask()
  242. {
  243. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  244. var member = doc?.SelectedStructureMember;
  245. if (member is null || !member.HasMaskBindable)
  246. return;
  247. doc!.Operations.ApplyMask(member);
  248. }
  249. [Command.Basic("PixiEditor.Layer.ToggleVisible", "TOGGLE_VISIBILITY", "TOGGLE_VISIBILITY", CanExecute = "PixiEditor.HasDocument")]
  250. public void ToggleVisible()
  251. {
  252. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  253. var member = doc?.SelectedStructureMember;
  254. if (member is null)
  255. return;
  256. member.IsVisibleBindable = !member.IsVisibleBindable;
  257. }
  258. [Evaluator.CanExecute("PixiEditor.Layer.HasMemberAbove")]
  259. public bool HasMemberAbove(object property) => HasSelectedMember(true);
  260. [Evaluator.CanExecute("PixiEditor.Layer.HasMemberBelow")]
  261. public bool HasMemberBelow(object property) => HasSelectedMember(false);
  262. [Command.Basic("PixiEditor.Layer.MoveSelectedMemberUpwards", "MOVE_MEMBER_UP", "MOVE_MEMBER_UP_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberAbove")]
  263. public void MoveSelectedMemberUpwards() => MoveSelectedMember(true);
  264. [Command.Basic("PixiEditor.Layer.MoveSelectedMemberDownwards", "MOVE_MEMBER_DOWN", "MOVE_MEMBER_DOWN_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberBelow")]
  265. public void MoveSelectedMemberDownwards() => MoveSelectedMember(false);
  266. [Command.Basic("PixiEditor.Layer.MergeSelected", "MERGE_ALL_SELECTED_LAYERS", "MERGE_ALL_SELECTED_LAYERS", CanExecute = "PixiEditor.Layer.HasMultipleSelectedMembers")]
  267. public void MergeSelected()
  268. {
  269. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  270. if (doc is null)
  271. return;
  272. var selected = GetSelected();
  273. if (selected.Count == 0)
  274. return;
  275. doc.Operations.MergeStructureMembers(selected);
  276. }
  277. public void MergeSelectedWith(bool above)
  278. {
  279. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  280. var member = doc?.SelectedStructureMember;
  281. if (doc is null || member is null)
  282. return;
  283. var (child, parent) = doc.StructureHelper.FindChildAndParent(member.GuidValue);
  284. if (child is null || parent is null)
  285. return;
  286. int index = parent.Children.IndexOf(child);
  287. if (!above && index == 0)
  288. return;
  289. if (above && index == parent.Children.Count - 1)
  290. return;
  291. doc.Operations.MergeStructureMembers(new List<Guid> { member.GuidValue, above ? parent.Children[index + 1].GuidValue : parent.Children[index - 1].GuidValue });
  292. }
  293. [Command.Basic("PixiEditor.Layer.MergeWithAbove", "MERGE_WITH_ABOVE", "MERGE_WITH_ABOVE_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberAbove")]
  294. public void MergeWithAbove() => MergeSelectedWith(true);
  295. [Command.Basic("PixiEditor.Layer.MergeWithBelow", "MERGE_WITH_BELOW", "MERGE_WITH_BELOW_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.HasMemberBelow", Icon = "Merge-downwards.png")]
  296. public void MergeWithBelow() => MergeSelectedWith(false);
  297. [Evaluator.CanExecute("PixiEditor.Layer.ReferenceLayerExists")]
  298. public bool ReferenceLayerExists() => Owner.DocumentManagerSubViewModel.ActiveDocument?.ReferenceLayerViewModel.ReferenceBitmap is not null;
  299. [Evaluator.CanExecute("PixiEditor.Layer.ReferenceLayerDoesntExist")]
  300. public bool ReferenceLayerDoesntExist() =>
  301. Owner.DocumentManagerSubViewModel.ActiveDocument is not null && Owner.DocumentManagerSubViewModel.ActiveDocument.ReferenceLayerViewModel.ReferenceBitmap is null;
  302. [Command.Basic("PixiEditor.Layer.ImportReferenceLayer", "ADD_REFERENCE_LAYER", "ADD_REFERENCE_LAYER", CanExecute = "PixiEditor.Layer.ReferenceLayerDoesntExist",
  303. Icon = PixiPerfectIcons.AddReference)]
  304. public async Task ImportReferenceLayer()
  305. {
  306. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  307. if (doc is null)
  308. return;
  309. string path = await OpenReferenceLayerFilePicker();
  310. if (path is null)
  311. return;
  312. WriteableBitmap bitmap;
  313. try
  314. {
  315. bitmap = Importer.ImportWriteableBitmap(path);
  316. }
  317. catch (RecoverableException e)
  318. {
  319. NoticeDialog.Show(title: "ERROR", message: e.DisplayMessage);
  320. return;
  321. }
  322. byte[] pixels = bitmap.ExtractPixels();
  323. VecI size = new VecI(bitmap.PixelSize.Width, bitmap.PixelSize.Height);
  324. doc.Operations.ImportReferenceLayer(
  325. pixels.ToImmutableArray(),
  326. size);
  327. }
  328. private async Task<string> OpenReferenceLayerFilePicker()
  329. {
  330. var imagesFilter = new FileTypeDialogDataSet(FileTypeDialogDataSet.SetKind.Images).GetFormattedTypes(true);
  331. if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  332. {
  333. var filePicker = await desktop.MainWindow.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions()
  334. {
  335. Title = new LocalizedString("REFERENCE_LAYER_PATH"),
  336. FileTypeFilter = imagesFilter,
  337. });
  338. if (filePicker is null || filePicker.Count == 0)
  339. return null;
  340. return filePicker[0].Path.LocalPath;
  341. }
  342. return null;
  343. }
  344. [Command.Basic("PixiEditor.Layer.DeleteReferenceLayer", "DELETE_REFERENCE_LAYER", "DELETE_REFERENCE_LAYER", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", Icon = "PixiPerfectIcons.Trash")]
  345. public void DeleteReferenceLayer()
  346. {
  347. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  348. if (doc is null)
  349. return;
  350. doc.Operations.DeleteReferenceLayer();
  351. }
  352. [Command.Basic("PixiEditor.Layer.TransformReferenceLayer", "TRANSFORM_REFERENCE_LAYER", "TRANSFORM_REFERENCE_LAYER", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", Icon = "crop.png")]
  353. public void TransformReferenceLayer()
  354. {
  355. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  356. if (doc is null)
  357. return;
  358. doc.Operations.TransformReferenceLayer();
  359. }
  360. [Command.Basic("PixiEditor.Layer.ToggleReferenceLayerTopMost", "TOGGLE_REFERENCE_LAYER_POS", "TOGGLE_REFERENCE_LAYER_POS_DESCRIPTIVE", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", IconEvaluator = "PixiEditor.Layer.ToggleReferenceLayerTopMostIcon")]
  361. public void ToggleReferenceLayerTopMost()
  362. {
  363. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  364. if (doc is null)
  365. return;
  366. doc.ReferenceLayerViewModel.IsTopMost = !doc.ReferenceLayerViewModel.IsTopMost;
  367. }
  368. [Command.Basic("PixiEditor.Layer.ResetReferenceLayerPosition", "RESET_REFERENCE_LAYER_POS", "RESET_REFERENCE_LAYER_POS", CanExecute = "PixiEditor.Layer.ReferenceLayerExists", Icon = "Layout.png")]
  369. public void ResetReferenceLayerPosition()
  370. {
  371. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  372. if (doc is null)
  373. return;
  374. doc.Operations.ResetReferenceLayerPosition();
  375. }
  376. [Evaluator.Icon("PixiEditor.Layer.ToggleReferenceLayerTopMostIcon")]
  377. public IImage GetAboveEverythingReferenceLayerIcon()
  378. {
  379. var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
  380. if (doc is null || doc.ReferenceLayerViewModel.IsTopMost)
  381. {
  382. return ImagePathToBitmapConverter.LoadBitmapFromRelativePath("/Images/ReferenceLayerBelow.png");
  383. }
  384. return ImagePathToBitmapConverter.LoadBitmapFromRelativePath("/Images/ReferenceLayerAbove.png");
  385. }
  386. }