From 8a4bf0ab7135eba42df0d347a24e2d7a40dc416d Mon Sep 17 00:00:00 2001 From: REghZy <32274404+AngryCarrot789@users.noreply.github.com> Date: Mon, 5 Feb 2024 00:04:52 +0000 Subject: [PATCH] Fixed ResourceAVMedia not being serialisable and also resources not being destroyed when a project is closed (causing things like file handles to stay open until closing the editor), added online/offline indicator in resource manager (red outline + red header), replaced standard message box usage with custom one (for the dark theme :D), added frame progress text block to export dialog --- FramePFX/Actions/ActionManager.cs | 4 +- FramePFX/App.xaml.cs | 9 ++- .../ObservableSelectionHelper.cs | 6 +- FramePFX/Editors/Actions/NewProjectAction.cs | 11 ++- .../Explorers/ResourceExplorerListControl.cs | 2 +- .../Explorers/ResourceExplorerListItem.cs | 25 ++++++- .../Controls/Resources/ResourceStyles.xaml | 21 +++++- .../Resources/Trees/ResourceTreeView.cs | 3 +- .../Resources/Trees/ResourceTreeViewItem.cs | 3 +- .../Controls/Timelines/TrackDropRegistry.cs | 8 +- .../Controls/ExportProgressDialog.xaml | 2 + .../Controls/ExportProgressDialog.xaml.cs | 12 +-- .../Editors/Factories/ResourceTypeFactory.cs | 1 + .../Controls/InvalidImagePathEntryControl.cs | 2 +- .../ResourceManaging/ResourceFolder.cs | 9 +++ .../ResourceManaging/ResourceManager.cs | 12 +-- .../Resources/ResourceAVMedia.cs | 12 +++ FramePFX/Editors/Timelines/Clips/Clip.cs | 5 ++ FramePFX/Editors/Timelines/Clips/ClipGroup.cs | 73 +++++++++++++++++++ FramePFX/FramePFX.csproj | 1 + 20 files changed, 181 insertions(+), 40 deletions(-) create mode 100644 FramePFX/Editors/Timelines/Clips/ClipGroup.cs diff --git a/FramePFX/Actions/ActionManager.cs b/FramePFX/Actions/ActionManager.cs index 9f6e168c..3e116f30 100644 --- a/FramePFX/Actions/ActionManager.cs +++ b/FramePFX/Actions/ActionManager.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using System.Windows; using FramePFX.Interactivity.DataContexts; +using FramePFX.Utils; namespace FramePFX.Actions { /// @@ -143,8 +144,7 @@ private static async Task TryExecuteOrShowDialog(AnAction action, AnActionEventA await action.ExecuteAsync(e); } catch (Exception ex) { - MessageBox.Show($"An exception occurred while executing '{e.ActionId ?? action.GetType().ToString()}':\n{ex.Message}", "Action execution exception"); - // await IoC.DialogService.ShowMessageExAsync("Action execution exception", , ex.GetToString()); + IoC.MessageService.ShowMessage("Action execution exception", $"An exception occurred while executing '{e.ActionId ?? action.GetType().ToString()}'", ex.GetToString()); } } diff --git a/FramePFX/App.xaml.cs b/FramePFX/App.xaml.cs index 87855d88..6661182b 100644 --- a/FramePFX/App.xaml.cs +++ b/FramePFX/App.xaml.cs @@ -70,7 +70,9 @@ public async Task InitWPFApp() { RuntimeHelpers.RunClassConstructor(typeof(UIInputManager).TypeHandle); + // This is where services are registered await ApplicationCore.InternalSetupNewInstance(this.splash); + // Most if not all services are available below here await AppLogger.Instance.FlushEntries(); await this.splash.SetAction("Loading shortcuts and actions...", null); @@ -90,11 +92,11 @@ public async Task InitWPFApp() { } } catch (Exception ex) { - MessageBox.Show("Failed to read keymap file" + keymapFilePath + ":" + ex.GetToString()); + IoC.MessageService.ShowMessage("Keymap", "Failed to read keymap file" + keymapFilePath + ":" + ex.GetToString()); } } else { - MessageBox.Show("Keymap file does not exist at " + keymapFilePath); + IoC.MessageService.ShowMessage("Keymap", "Keymap file does not exist at " + keymapFilePath); } await this.splash.SetAction("Loading FFmpeg...", null); @@ -103,8 +105,7 @@ public async Task InitWPFApp() { ffmpeg.avdevice_register_all(); } catch (Exception e) { - MessageBox.Show("The FFmpeg libraries (avcodec-60.dll, avfilter-9, and all other 6 dlls files) must be placed in the build folder which is where the EXE is, e.g. /FramePFX/bin/x64/Debug", "FFmpeg not found"); - throw new Exception("FFmpeg Unavailable. Copy FFmpeg DLLs into the same folder as the app's .exe", e); + IoC.MessageService.ShowMessage("FFmpeg registration failed", "The FFmpeg libraries (avcodec-60.dll, avfilter-9, and all other 6 dlls files) must be placed in the build folder which is where the EXE is, e.g. /FramePFX/bin/x64/Debug", e.GetToString()); } } } diff --git a/FramePFX/AttachedProperties/ObservableSelectionHelper.cs b/FramePFX/AttachedProperties/ObservableSelectionHelper.cs index 1d9fbcb6..feb2cef1 100644 --- a/FramePFX/AttachedProperties/ObservableSelectionHelper.cs +++ b/FramePFX/AttachedProperties/ObservableSelectionHelper.cs @@ -330,9 +330,9 @@ private static void OnSourceCollectionChanged(object sender, NotifyCollectionCha } if (list.TryGetException(out Exception error)) { - MessageBox.Show("An exception occurred while processing selection change. " + - "This may have corrupted the application in some way, so please restart.\n\n" + - "See the app logs for more info", "Error"); + IoC.MessageService.ShowMessage("Error", "An exception occurred while processing selection change. " + + "This may have corrupted the application in some way, so please restart.\n\n" + + "See the app logs for more info"); } } } diff --git a/FramePFX/Editors/Actions/NewProjectAction.cs b/FramePFX/Editors/Actions/NewProjectAction.cs index 5ea7d42a..66e3e981 100644 --- a/FramePFX/Editors/Actions/NewProjectAction.cs +++ b/FramePFX/Editors/Actions/NewProjectAction.cs @@ -1,6 +1,8 @@ using System.Threading.Tasks; using System.Windows; using FramePFX.Actions; +using FramePFX.Editors.Timelines.Effects; +using FramePFX.Editors.Timelines.Tracks; using FramePFX.Interactivity.DataContexts; using FramePFX.Views; @@ -44,7 +46,14 @@ public override Task ExecuteAsync(AnActionEventArgs e) { return Task.CompletedTask; } - editor.SetProject(new Project()); + Project project = new Project(); + VideoTrack track = new VideoTrack() { + DisplayName = "Video Track 1" + }; + + track.AddEffect(new MotionEffect()); + project.MainTimeline.AddTrack(track); + editor.SetProject(project); return Task.CompletedTask; } } diff --git a/FramePFX/Editors/Controls/Resources/Explorers/ResourceExplorerListControl.cs b/FramePFX/Editors/Controls/Resources/Explorers/ResourceExplorerListControl.cs index 7332d95e..a5d33c3f 100644 --- a/FramePFX/Editors/Controls/Resources/Explorers/ResourceExplorerListControl.cs +++ b/FramePFX/Editors/Controls/Resources/Explorers/ResourceExplorerListControl.cs @@ -162,7 +162,7 @@ protected override async void OnDrop(DragEventArgs e) { await ResourceDropRegistry.DropRegistry.OnDropped(currentFolder, list, effects); } else if (!await ResourceDropRegistry.DropRegistry.OnDroppedNative(currentFolder, new DataObjectWrapper(e.Data), effects)) { - MessageBox.Show("Unknown dropped item. Drop files here", "Unknown data"); + IoC.MessageService.ShowMessage("Unknown data", "Unknown dropped item. Drop files here"); // await IoC.DialogService.ShowMessageAsync("Unknown data", "Unknown dropped item. Drop files here"); } } diff --git a/FramePFX/Editors/Controls/Resources/Explorers/ResourceExplorerListItem.cs b/FramePFX/Editors/Controls/Resources/Explorers/ResourceExplorerListItem.cs index e8292fb9..3ab911e0 100644 --- a/FramePFX/Editors/Controls/Resources/Explorers/ResourceExplorerListItem.cs +++ b/FramePFX/Editors/Controls/Resources/Explorers/ResourceExplorerListItem.cs @@ -21,6 +21,8 @@ public class ResourceExplorerListItem : ContentControl { public static readonly DependencyProperty IsDroppableTargetOverProperty = DependencyProperty.Register("IsDroppableTargetOver", typeof(bool), typeof(ResourceExplorerListItem), new PropertyMetadata(BoolBox.False)); public static readonly DependencyProperty IsSelectedProperty = Selector.IsSelectedProperty.AddOwner(typeof(ResourceExplorerListItem), new FrameworkPropertyMetadata(BoolBox.False, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (d, e) => ((ResourceExplorerListItem) d).OnIsSelectedChanged((bool) e.NewValue))); public static readonly DependencyProperty DisplayNameProperty = DependencyProperty.Register("DisplayName", typeof(string), typeof(ResourceExplorerListItem), new PropertyMetadata(null)); + private static readonly DependencyPropertyKey IsResourceOnlinePropertyKey = DependencyProperty.RegisterReadOnly("IsResourceOnline", typeof(bool), typeof(ResourceExplorerListItem), new PropertyMetadata(BoolBox.True)); + public static readonly DependencyProperty IsResourceOnlineProperty = IsResourceOnlinePropertyKey.DependencyProperty; public bool IsDroppableTargetOver { get => (bool) this.GetValue(IsDroppableTargetOverProperty); @@ -29,7 +31,7 @@ public bool IsDroppableTargetOver { public bool IsSelected { get => (bool) this.GetValue(IsSelectedProperty); - set => this.SetValue(IsSelectedProperty, value); + set => this.SetValue(IsSelectedProperty, value.Box()); } public string DisplayName { @@ -37,6 +39,11 @@ public string DisplayName { set => this.SetValue(DisplayNameProperty, value); } + public bool IsResourceOnline { + get => (bool) this.GetValue(IsResourceOnlineProperty); + private set => this.SetValue(IsResourceOnlinePropertyKey, value.Box()); + } + public BaseResource Model { get; private set; } public ResourceExplorerListControl ResourceExplorerList { get; private set; } @@ -195,8 +202,7 @@ protected override async void OnDrop(DragEventArgs e) { await ResourceDropRegistry.DropRegistry.OnDropped(folder, list, effects); } else if (!await ResourceDropRegistry.DropRegistry.OnDroppedNative(folder, new DataObjectWrapper(e.Data), effects)) { - MessageBox.Show("Unknown dropped item. Drop files here", "Unknown data"); - // await IoC.DialogService.ShowMessageAsync("Unknown data", "Unknown dropped item. Drop files here"); + IoC.MessageService.ShowMessage("Unknown Data", "Unknown dropped item. Drop files here"); } } finally { @@ -258,6 +264,11 @@ public void OnAddingToList(ResourceExplorerListControl explorerList, BaseResourc public void OnAddedToList() { this.displayNameBinder.Attach(this, this.Model); this.isSelectedBinder.Attach(this, this.Model); + if (this.Model is ResourceItem item) { + item.OnlineStateChanged += this.UpdateIsOnlineState; + this.UpdateIsOnlineState(item); + } + ResourceExplorerListItemContent content = (ResourceExplorerListItemContent) this.Content; content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); @@ -270,6 +281,10 @@ public void OnAddedToList() { public void OnRemovingFromList() { this.displayNameBinder.Detatch(); this.isSelectedBinder.Detatch(); + if (this.Model is ResourceItem item) { + item.OnlineStateChanged -= this.UpdateIsOnlineState; + } + ResourceExplorerListItemContent content = (ResourceExplorerListItemContent) this.Content; content.Disconnect(); this.Content = null; @@ -281,5 +296,9 @@ public void OnRemovedFromList() { this.ResourceExplorerList = null; this.Model = null; } + + private void UpdateIsOnlineState(ResourceItem resource) { + this.IsResourceOnline = resource.IsOnline; + } } } \ No newline at end of file diff --git a/FramePFX/Editors/Controls/Resources/ResourceStyles.xaml b/FramePFX/Editors/Controls/Resources/ResourceStyles.xaml index 926afa0b..012d4431 100644 --- a/FramePFX/Editors/Controls/Resources/ResourceStyles.xaml +++ b/FramePFX/Editors/Controls/Resources/ResourceStyles.xaml @@ -109,10 +109,15 @@ - + + + + + - + + @@ -138,6 +143,18 @@ + + + + + + + + + + + + diff --git a/FramePFX/Editors/Controls/Resources/Trees/ResourceTreeView.cs b/FramePFX/Editors/Controls/Resources/Trees/ResourceTreeView.cs index 0dd58bcb..4f7d65ba 100644 --- a/FramePFX/Editors/Controls/Resources/Trees/ResourceTreeView.cs +++ b/FramePFX/Editors/Controls/Resources/Trees/ResourceTreeView.cs @@ -147,8 +147,7 @@ protected override async void OnDrop(DragEventArgs e) { await ResourceDropRegistry.DropRegistry.OnDropped(manager.RootContainer, list, effects); } else if (!await ResourceDropRegistry.DropRegistry.OnDroppedNative(manager.RootContainer, new DataObjectWrapper(e.Data), effects)) { - MessageBox.Show("Unknown dropped item. Drop files here", "Unknown data"); - // await IoC.DialogService.ShowMessageAsync("Unknown data", "Unknown dropped item. Drop files here"); + IoC.MessageService.ShowMessage("Unknown Data", "Unknown dropped item. Drop files here"); } } finally { diff --git a/FramePFX/Editors/Controls/Resources/Trees/ResourceTreeViewItem.cs b/FramePFX/Editors/Controls/Resources/Trees/ResourceTreeViewItem.cs index da8ef17f..8d66aa27 100644 --- a/FramePFX/Editors/Controls/Resources/Trees/ResourceTreeViewItem.cs +++ b/FramePFX/Editors/Controls/Resources/Trees/ResourceTreeViewItem.cs @@ -324,8 +324,7 @@ protected override async void OnDrop(DragEventArgs e) { await ResourceDropRegistry.DropRegistry.OnDropped(self, list, effects); } else if (!await ResourceDropRegistry.DropRegistry.OnDroppedNative(self, new DataObjectWrapper(e.Data), effects)) { - MessageBox.Show("Unknown dropped item. Drop files here", "Unknown data"); - // await IoC.DialogService.ShowMessageAsync("Unknown data", "Unknown dropped item. Drop files here"); + IoC.MessageService.ShowMessage("Unknown Data", "Unknown dropped item. Drop files here"); } } finally { diff --git a/FramePFX/Editors/Controls/Timelines/TrackDropRegistry.cs b/FramePFX/Editors/Controls/Timelines/TrackDropRegistry.cs index 5780e4e7..d15cb8fa 100644 --- a/FramePFX/Editors/Controls/Timelines/TrackDropRegistry.cs +++ b/FramePFX/Editors/Controls/Timelines/TrackDropRegistry.cs @@ -21,7 +21,7 @@ static TrackDropRegistry() { return objekt.GetData(NativeDropTypes.FileDrop) is string[] files && files.Length > 0 ? EnumDropType.Copy : EnumDropType.None; }, async (model, objekt, type, c) => { string[] files = (string[]) objekt.GetData(NativeDropTypes.FileDrop); - MessageBox.Show($"Dropping files directly into the timeline is not implemented yet.\nYou dropped: {string.Join(", ", files)}", "STILL TODO"); + IoC.MessageService.ShowMessage("STILL TODO", $"Dropping files directly into the timeline is not implemented yet.\nYou dropped: {string.Join(", ", files)}"); }); DropRegistry.Register((track, resource, dt, ctx) => { @@ -30,17 +30,17 @@ static TrackDropRegistry() { : EnumDropType.None; }, async (track, resource, dt, ctx) => { if (!ctx.TryGetContext(DataKeys.TrackDropFrameKey, out long frame)) { - MessageBox.Show("Drag drop error: no track frame location", "Drop err"); + IoC.MessageService.ShowMessage("Drop err", "Drag drop error: no track frame location"); return; } if (!resource.IsOnline) { - MessageBox.Show("Cannot add an offline resource to the timeline", "Resource Offline"); + IoC.MessageService.ShowMessage("Resource Offline", "Cannot add an offline resource to the timeline"); return; } if (resource.UniqueId == ResourceManager.EmptyId || !resource.IsRegistered()) { - MessageBox.Show("This resource is not registered yet. This is a bug", "Invalid resource"); + IoC.MessageService.ShowMessage("Invalid resource", "This resource is not registered yet. This is a bug"); return; } diff --git a/FramePFX/Editors/Exporting/Controls/ExportProgressDialog.xaml b/FramePFX/Editors/Exporting/Controls/ExportProgressDialog.xaml index 55894067..a016cf73 100644 --- a/FramePFX/Editors/Exporting/Controls/ExportProgressDialog.xaml +++ b/FramePFX/Editors/Exporting/Controls/ExportProgressDialog.xaml @@ -15,6 +15,8 @@ + + diff --git a/FramePFX/Editors/Exporting/Controls/ExportProgressDialog.xaml.cs b/FramePFX/Editors/Exporting/Controls/ExportProgressDialog.xaml.cs index 3543920d..b4d3ab00 100644 --- a/FramePFX/Editors/Exporting/Controls/ExportProgressDialog.xaml.cs +++ b/FramePFX/Editors/Exporting/Controls/ExportProgressDialog.xaml.cs @@ -27,8 +27,6 @@ public partial class ExportProgressDialog : WindowEx, IExportProgress { public CancellationTokenSource Cancellation { get; } - private bool isCancelled; - public ExportProgressDialog(FrameSpan renderSpan, CancellationTokenSource cancellation) { this.renderSpan = renderSpan; this.InitializeComponent(); @@ -36,15 +34,19 @@ public ExportProgressDialog(FrameSpan renderSpan, CancellationTokenSource cancel this.Cancellation = cancellation; this.currentRenderFrame = renderSpan.Begin; this.currentEncodeFrame = renderSpan.Begin; - this.rapidUpdateRender = new RapidDispatchCallback(() => { - this.PART_RenderProgressBar.Value = this.RenderProgressPercentage; - }, "ExportUpdateRender"); + this.PART_FrameProgressText.Text = "0/" + (this.EndFrame - 1); + this.rapidUpdateRender = new RapidDispatchCallback(this.UpdateRenderedFrame, "ExportUpdateRender"); this.rapidUpdateEncode = new RapidDispatchCallback(() => { this.PART_EncodeProgressBar.Value = this.EncodeProgressPercentage; }, "ExportUpdateEncode"); } + private void UpdateRenderedFrame() { + this.PART_RenderProgressBar.Value = this.RenderProgressPercentage; + this.PART_FrameProgressText.Text = $"{this.currentRenderFrame}/{this.EndFrame - 1}"; + } + public void OnFrameRendered(long frame) { Interlocked.Increment(ref this.currentRenderFrame); this.rapidUpdateRender.InvokeAsync(); diff --git a/FramePFX/Editors/Factories/ResourceTypeFactory.cs b/FramePFX/Editors/Factories/ResourceTypeFactory.cs index e6aff955..7b8a4c71 100644 --- a/FramePFX/Editors/Factories/ResourceTypeFactory.cs +++ b/FramePFX/Editors/Factories/ResourceTypeFactory.cs @@ -10,6 +10,7 @@ private ResourceTypeFactory() { this.RegisterType("r_argb", typeof(ResourceColour)); this.RegisterType("r_img", typeof(ResourceImage)); this.RegisterType("r_txt", typeof(ResourceTextStyle)); + this.RegisterType("r_avmedia", typeof(ResourceAVMedia)); } public BaseResource NewResource(string id) { diff --git a/FramePFX/Editors/ResourceManaging/Autoloading/Controls/InvalidImagePathEntryControl.cs b/FramePFX/Editors/ResourceManaging/Autoloading/Controls/InvalidImagePathEntryControl.cs index 8bfdfac6..099ed72b 100644 --- a/FramePFX/Editors/ResourceManaging/Autoloading/Controls/InvalidImagePathEntryControl.cs +++ b/FramePFX/Editors/ResourceManaging/Autoloading/Controls/InvalidImagePathEntryControl.cs @@ -26,7 +26,7 @@ public override void OnApplyTemplate() { private void ConfirmClick(object sender, RoutedEventArgs e) { if (!this.Entry.TryLoad()) { - MessageBox.Show("File path is still invalid"); + IoC.MessageService.ShowMessage("No such file", "File path is still invalid"); } } diff --git a/FramePFX/Editors/ResourceManaging/ResourceFolder.cs b/FramePFX/Editors/ResourceManaging/ResourceFolder.cs index e74764f6..c5332aca 100644 --- a/FramePFX/Editors/ResourceManaging/ResourceFolder.cs +++ b/FramePFX/Editors/ResourceManaging/ResourceFolder.cs @@ -151,5 +151,14 @@ public static void DestroyHierarchy(BaseResource resource) { resource.Destroy(); } } + + public static void ClearHierarchy(BaseResource resource) { + if (resource is ResourceFolder folder) { + for (int i = folder.items.Count - 1; i >= 0; i--) { + ClearHierarchy(folder.items[i]); + folder.RemoveItemAt(i); + } + } + } } } \ No newline at end of file diff --git a/FramePFX/Editors/ResourceManaging/ResourceManager.cs b/FramePFX/Editors/ResourceManaging/ResourceManager.cs index 8416bb30..f25aea9d 100644 --- a/FramePFX/Editors/ResourceManaging/ResourceManager.cs +++ b/FramePFX/Editors/ResourceManaging/ResourceManager.cs @@ -231,16 +231,8 @@ public bool EntryExists(ResourceItem item, out bool isRefEqual) { } public void ClearEntries() { - using (ErrorList stack = new ErrorList()) { - foreach (KeyValuePair entry in this.uuidToItem.ToList()) { - try { - this.UnregisterItem(entry.Value); - } - catch (Exception e) { - stack.Add(e); - } - } - } + ResourceFolder.DestroyHierarchy(this.RootContainer); + ResourceFolder.ClearHierarchy(this.RootContainer); } #region Static Helper Functions diff --git a/FramePFX/Editors/ResourceManaging/Resources/ResourceAVMedia.cs b/FramePFX/Editors/ResourceManaging/Resources/ResourceAVMedia.cs index 992296f4..4a824ecd 100644 --- a/FramePFX/Editors/ResourceManaging/Resources/ResourceAVMedia.cs +++ b/FramePFX/Editors/ResourceManaging/Resources/ResourceAVMedia.cs @@ -9,6 +9,7 @@ using FramePFX.FFmpegWrapper.Codecs; using FramePFX.FFmpegWrapper.Containers; using FramePFX.Logger; +using FramePFX.RBC; using FramePFX.Utils; namespace FramePFX.Editors.ResourceManaging.Resources { @@ -42,6 +43,17 @@ public ResourceAVMedia() { } + public override void WriteToRBE(RBEDictionary data) { + base.WriteToRBE(data); + if (!string.IsNullOrEmpty(this.filePath)) + data.SetString(nameof(this.FilePath), this.filePath); + } + + public override void ReadFromRBE(RBEDictionary data) { + base.ReadFromRBE(data); + this.filePath = data.GetString(nameof(this.FilePath), null); + } + protected override bool OnTryAutoEnable(ResourceLoader loader) { if (string.IsNullOrEmpty(this.FilePath)) { return true; diff --git a/FramePFX/Editors/Timelines/Clips/Clip.cs b/FramePFX/Editors/Timelines/Clips/Clip.cs index ffacdb83..ce21b7c2 100644 --- a/FramePFX/Editors/Timelines/Clips/Clip.cs +++ b/FramePFX/Editors/Timelines/Clips/Clip.cs @@ -29,6 +29,8 @@ public abstract class Clip : IDisplayName, IAutomatable, ITransferableData, IStr private AutomationSequence activeSequence; private long mediaFrameOffset; + private ClipGroup myGroup; + /// /// Gets the track that this clip is placed in /// @@ -467,5 +469,8 @@ internal static void InternalOnTimelineProjectChanged(Clip clip, Project oldProj clip.Project = newProject; clip.OnProjectChanged(oldProject, newProject); } + + internal static ClipGroup InternalGetGroup(Clip clip) => clip.myGroup; + internal static void InternalSetGroup(Clip clip, ClipGroup group) => clip.myGroup = group; } } \ No newline at end of file diff --git a/FramePFX/Editors/Timelines/Clips/ClipGroup.cs b/FramePFX/Editors/Timelines/Clips/ClipGroup.cs new file mode 100644 index 00000000..e63d9bd1 --- /dev/null +++ b/FramePFX/Editors/Timelines/Clips/ClipGroup.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; + +namespace FramePFX.Editors.Timelines.Clips { + /// + /// A class used to manage a collection of clips where location changes are synchronised + /// + public class ClipGroup { + private readonly List info; + + public ClipGroup() { + this.info = new List(); + } + + public static ClipGroup CreateOrMergeGroups(List newClips) { + ClipGroup finalGroup = null; + foreach (Clip clip in newClips) { + ClipGroup group = Clip.InternalGetGroup(clip); + if (group != null) { + if (finalGroup == null) { + finalGroup = group; + } + else if (finalGroup != group) { + group.RemoveClip(clip); + finalGroup.AddClip(clip); + } + } + else { + if (finalGroup == null) + finalGroup = new ClipGroup(); + + finalGroup.AddClip(clip); + } + } + + return finalGroup; + } + + private void AddClip(Clip clip) { + if (this.info.Contains(clip)) + throw new Exception("Clip already contained in this group"); + if (Clip.InternalGetGroup(clip) != null) + throw new Exception("Clip already has a group"); + Clip.InternalSetGroup(clip, this); + this.info.Add(clip); + } + + private void RemoveClip(Clip clip) { + ClipGroup oldgroup = Clip.InternalGetGroup(clip); + if (oldgroup == null) + throw new Exception("Clip did not have a group"); + if (oldgroup != this) + throw new Exception("Clip's group did not match the current instance"); + if (!this.info.Remove(clip)) + throw new Exception("Fatal error: list did not contain clip"); + Clip.InternalSetGroup(clip, null); + } + + private void MoveClipToGroup(Clip clip, ClipGroup newGroup) { + ClipGroup oldgroup = Clip.InternalGetGroup(clip); + oldgroup?.RemoveClip(clip); + newGroup?.AddClip(clip); + } + + public static void AddClipToGroup(Clip clip) { + + } + + private class ClipData { + + } + } +} \ No newline at end of file diff --git a/FramePFX/FramePFX.csproj b/FramePFX/FramePFX.csproj index 9ecb0e63..c95af5ad 100644 --- a/FramePFX/FramePFX.csproj +++ b/FramePFX/FramePFX.csproj @@ -186,6 +186,7 @@ +