Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/TextForge.Core/Engine/DocumentEngine.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TextForge.Core.Documents;
using TextForge.Core.Modules;
using TextForge.Core.Templates;
Expand All @@ -9,6 +6,8 @@ namespace TextForge.Core.Engine;

public class DocumentEngine : IDocumentEngine
{
private readonly DefaultTemplate _defaultTemplate = new();

public RenderTree Evaluate(Document document, DefaultTemplate template)
{
ArgumentNullException.ThrowIfNull(document);
Expand Down
20 changes: 15 additions & 5 deletions src/TextForge.Core/Templates/DefaultTemblate.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using TextForge.Core.Engine;
using TextForge.Core.Modules;
Expand All @@ -8,10 +9,13 @@ public class DefaultTemplate
{
public string Name { get; init; } = "Default";

/// <summary>
/// Global template baseline defaults. Default text color is standard blue.
/// </summary>
public ModuleFeatures DocumentDefaults { get; init; } = new()
{
Font = "Segoe UI",
Color = "#1F2937",
Color = "#2563EB", // Standard Template Blue
FontWeight = ModuleFontWeight.Normal,
LineSpacing = 1.2
};
Expand Down Expand Up @@ -71,7 +75,7 @@ public class DefaultTemplate
}
};

public Dictionary<string, ModuleFeatures> NamedStyles { get; init; } = new()
public Dictionary<string, ModuleFeatures> NamedStyles { get; init; } = new(StringComparer.OrdinalIgnoreCase)
{
["Title"] = new ModuleFeatures
{
Expand All @@ -95,7 +99,7 @@ public class DefaultTemplate
}
};

public Dictionary<string, LayoutProperties> NamedLayoutStyles { get; init; } = new()
public Dictionary<string, LayoutProperties> NamedLayoutStyles { get; init; } = new(StringComparer.OrdinalIgnoreCase)
{
["Title"] = new LayoutProperties
{
Expand All @@ -120,14 +124,17 @@ public class DefaultTemplate

public ModuleFeatures ResolveFeatures(Module module)
{
ArgumentNullException.ThrowIfNull(module);

var resolved = DocumentDefaults;

if (TypeDefaults.TryGetValue(module.Type, out var typeStyle))
{
resolved = typeStyle.MergeWith(resolved);
}

if (module.StyleKey is not null && NamedStyles.TryGetValue(module.StyleKey, out var namedStyle))
var key = module.StyleKey ?? module.Name;
if (!string.IsNullOrWhiteSpace(key) && NamedStyles.TryGetValue(key, out var namedStyle))
{
resolved = namedStyle.MergeWith(resolved);
}
Expand All @@ -137,14 +144,17 @@ public ModuleFeatures ResolveFeatures(Module module)

public LayoutProperties ResolveLayout(Module module)
{
ArgumentNullException.ThrowIfNull(module);

var resolved = DocumentLayoutDefaults;

if (TypeLayoutDefaults.TryGetValue(module.Type, out var typeLayout))
{
resolved = typeLayout.MergeWith(resolved);
}

if (module.StyleKey is not null && NamedLayoutStyles.TryGetValue(module.StyleKey, out var namedLayout))
var key = module.StyleKey ?? module.Name;
if (!string.IsNullOrWhiteSpace(key) && NamedLayoutStyles.TryGetValue(key, out var namedLayout))
{
resolved = namedLayout.MergeWith(resolved);
}
Expand Down
16 changes: 16 additions & 0 deletions src/TextForge.Desktop/Views/Components/ModuleEditorView.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System;
using Avalonia.Controls;
using Avalonia.Interactivity;
using TextForge.Core.Documents;
using TextForge.Core.Modules;

namespace TextForge.Desktop.Views.Components;

public partial class ModuleEditorView : UserControl
{
private Document? _document;
public event EventHandler<Module>? ModuleMoveUpRequested;
public event EventHandler<Module>? ModuleMoveDownRequested;
public event EventHandler<Module>? ModuleDeleteRequested;
Expand Down Expand Up @@ -39,4 +41,18 @@ private void DeleteModuleButton_Click(object? sender, RoutedEventArgs e)
ModuleDeleteRequested?.Invoke(this, module);
}
}
/// <summary>
/// Binds the editor to a document instance.
/// Done once per document load.
/// </summary>
public void SetDocument(Document document)
{
if (_document == document) return;

_document = document;

// Assign the collection directly ONCE.
// ObservableCollection will handle all subsequent Add/Remove operations automatically.
ModuleListBox.ItemsSource = _document.Modules;
}
}
2 changes: 1 addition & 1 deletion tests/TextForge.Tests/DefaultTemplateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public void ResolveFeatures_StandardTextModule_InheritsDocumentAndTypeDefaults()

// Assert: gets baseline font and color from document defaults + type line spacing
Assert.Equal("Segoe UI", features.Font);
Assert.Equal("#1F2937", features.Color);
Assert.Equal("#2563EB", features.Color);
Assert.Equal(ModuleFontWeight.Normal, features.FontWeight);
Assert.Equal(1.2, features.LineSpacing);
Assert.False(features.Italic);
Expand Down
62 changes: 62 additions & 0 deletions tests/TextForge.Tests/TemplateResolutionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Xunit;
using TextForge.Core.Documents;
using TextForge.Core.Engine;
using TextForge.Core.Modules;
using TextForge.Core.Templates;

namespace TextForge.Tests;

public class TemplateResolutionTests
{
private readonly DocumentEngine _engine = new();
private readonly DefaultTemplate _template = new();

[Fact]
public void Evaluate_DefaultDocument_AppliesTemplateBaselineBlueColor()
{
// Arrange
var doc = new Document("Test Showcase");
doc.AddModule(Module.CreateTitle("Welcome to TextForge"));
doc.AddModule(Module.CreateParagraph("Standard text body."));

// Act
var renderTree = _engine.Evaluate(doc, _template);

// Assert
Assert.Equal(2, renderTree.RootNodes.Count);
Assert.Equal("#2563EB", renderTree.RootNodes[0].Features.Color);
Assert.Equal("#2563EB", renderTree.RootNodes[1].Features.Color);
}

[Fact]
public void Evaluate_ModuleExplicitColorOverride_TakesPrecedenceOverTemplate()
{
// Arrange
var doc = new Document("Alert Test");
var alertModule = Module.CreateAlert("Warning Message", color: "#DC2626");
doc.AddModule(alertModule);

// Act
var renderTree = _engine.Evaluate(doc, _template);

// Assert
Assert.Single(renderTree.RootNodes);
Assert.Equal("#DC2626", renderTree.RootNodes[0].Features.Color);
}

[Fact]
public void Evaluate_UnknownOrCustomModule_FallsBackSafelyToBaselineColor()
{
// Arrange
var doc = new Document("Fallback Test");
var unknownModule = new Module("Custom content", ModuleType.Custom, name: "unsupported-custom-archetype");
doc.AddModule(unknownModule);

// Act
var renderTree = _engine.Evaluate(doc, _template);

// Assert
Assert.Single(renderTree.RootNodes);
Assert.Equal("#2563EB", renderTree.RootNodes[0].Features.Color);
}
}
Loading