diff --git a/src/TextForge.Core/Documents/Document.cs b/src/TextForge.Core/Documents/Document.cs index 80805cf..8cc9a22 100644 --- a/src/TextForge.Core/Documents/Document.cs +++ b/src/TextForge.Core/Documents/Document.cs @@ -173,6 +173,25 @@ private static bool RemoveRecursive(IList list, Module target) #endregion #region Reordering Operations + public void DetachModule(Module module) + { + ArgumentNullException.ThrowIfNull(module); + + if (module.Parent is not null) + { + // 1. Remove from parent collection (auto-sets module.Parent = null) + module.Parent.SubModules.Remove(module); + + // 2. Add to root document collection + Modules.Add(module); + } + + // 3. Update state + module.Detach(); + + // 4. Trigger document change pipeline + NotifyChanged(); + } /// /// Moves a module one position earlier within its current sibling list (root level or nested level). diff --git a/src/TextForge.Core/Engine/DocumentEngine.cs b/src/TextForge.Core/Engine/DocumentEngine.cs index 4b67577..98b0328 100644 --- a/src/TextForge.Core/Engine/DocumentEngine.cs +++ b/src/TextForge.Core/Engine/DocumentEngine.cs @@ -1,3 +1,4 @@ +using System.Linq; using TextForge.Core.Documents; using TextForge.Core.Modules; using TextForge.Core.Templates; @@ -14,6 +15,7 @@ public RenderTree Evaluate(Document document, DefaultTemplate template) ArgumentNullException.ThrowIfNull(template); var rootNodes = document.Modules + .Where(module => module.ConnectionState == ModuleConnectionState.Connected) // Skip detached root modules .Select(module => EvaluateModule(module, template)) .ToList(); @@ -34,6 +36,7 @@ private RenderNode EvaluateModule(Module module, DefaultTemplate template) var resolvedLayout = template.ResolveLayout(module); var children = module.SubModules + .Where(child => child.ConnectionState == ModuleConnectionState.Connected) // Skip detached child modules .Select(child => EvaluateModule(child, template)) .ToList(); diff --git a/src/TextForge.Core/Modules/Module.cs b/src/TextForge.Core/Modules/Module.cs index 1529cfd..1fc4d5c 100644 --- a/src/TextForge.Core/Modules/Module.cs +++ b/src/TextForge.Core/Modules/Module.cs @@ -13,6 +13,7 @@ public class Module : INotifyPropertyChanged private bool _isExpanded; private Module? _parent; + private ModuleConnectionState _connectionState = ModuleConnectionState.Connected; public event PropertyChangedEventHandler? PropertyChanged; public Guid Id { get; init; } = Guid.NewGuid(); @@ -109,6 +110,31 @@ private void WireSubModulesCollection() SubModules.CollectionChanged += OnSubModulesChanged; } + /// + /// Dictates whether the module is actively part of the document flow or temporarily detached. + /// Detached modules retain all data and children but are skipped during PDF rendering. + /// + public ModuleConnectionState ConnectionState + { + get => _connectionState; + set + { + if (_connectionState != value) + { + _connectionState = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(IsDetached)); + } + } + } + + [JsonIgnore] + public bool IsDetached => ConnectionState == ModuleConnectionState.Detached; + + // Helper toggle methods + public void Detach() => ConnectionState = ModuleConnectionState.Detached; + public void Connect() => ConnectionState = ModuleConnectionState.Connected; + private void OnSubModulesChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) diff --git a/src/TextForge.Core/Modules/ModuleConnectionState.cs b/src/TextForge.Core/Modules/ModuleConnectionState.cs new file mode 100644 index 0000000..23f61bd --- /dev/null +++ b/src/TextForge.Core/Modules/ModuleConnectionState.cs @@ -0,0 +1,17 @@ +namespace TextForge.Core.Modules; + +/// +/// Dictates whether a module participates in the document layout. +/// +public enum ModuleConnectionState +{ + /// + /// The module is part of the layout and will be rendered. + /// + Connected, + + /// + /// The module is preserved in the document model but excluded from layout and rendering. + /// + Detached +} diff --git a/src/TextForge.Desktop/Views/Components/ModuleEditorView.axaml b/src/TextForge.Desktop/Views/Components/ModuleEditorView.axaml index 098c357..675e4fd 100644 --- a/src/TextForge.Desktop/Views/Components/ModuleEditorView.axaml +++ b/src/TextForge.Desktop/Views/Components/ModuleEditorView.axaml @@ -90,13 +90,13 @@ Background="Transparent" Foreground="#EF4444" ToolTip.Tip="Delete Module" Tag="{Binding}" Click="DeleteModuleButton_Click" /> -