Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c081bea
Progress report
Nickelony Mar 14, 2026
c698cc4
Fixes and adjustments
Nickelony Mar 14, 2026
166414b
Cleanups
Nickelony Mar 14, 2026
b96ab71
Remove currently unusued and unrelated stuff
Nickelony Mar 14, 2026
b10adf7
Convert to vertical slices architecture 🍕
Nickelony Mar 14, 2026
7fb561d
Merge remote-tracking branch 'origin/develop' into Nickelony/NEW-WPF
Nickelony Mar 15, 2026
ba253d0
Fix build
Nickelony Mar 15, 2026
bfd566e
Merge branch 'develop' into Nickelony/NEW-WPF
Nickelony Apr 6, 2026
a199da4
Move Flyby timeline code into a vertical slice
Nickelony Apr 6, 2026
2e94816
Implement brightness setting compatibility for DarkUI.WPF
Nickelony Apr 6, 2026
57f5287
Fix indentation
Nickelony Apr 24, 2026
e25b3dc
Refactor RoomOptionsViewModel
Nickelony Apr 24, 2026
d0b7466
Adjustments
Nickelony Apr 25, 2026
6047777
Grids refactor
Nickelony Apr 25, 2026
aa879a5
Fixes
Nickelony Apr 25, 2026
539ee36
Add right-click panning
Nickelony Apr 25, 2026
4316121
Many improvements
Nickelony Apr 26, 2026
d1cd681
Remove unnecessary names
Nickelony Apr 26, 2026
989821d
Fix ComboBox widths
Nickelony Apr 26, 2026
4458308
Fix min heights
Nickelony Apr 26, 2026
aa49737
Merge branch 'develop' into Nickelony/NEW-WPF
Copilot Apr 26, 2026
7b73219
Fix WPF owner and utility regressions
Copilot Apr 26, 2026
dc35477
Address validation feedback for WPF regressions
Copilot Apr 26, 2026
fbdb572
Refine WPF owner helper usage
Copilot Apr 26, 2026
922bd66
Address final validation feedback
Copilot Apr 26, 2026
bd8dc5b
Validate command args before resolving owners
Copilot Apr 26, 2026
89c280b
Implement keyboard shortcut tooltips
Nickelony Apr 26, 2026
c1ba9d9
Remove stray `:`
Nickelony Apr 26, 2026
3f7bfac
Update AGENTS.md
Nickelony Apr 27, 2026
9f8f027
Apply AGENTS compliance fixes
Copilot Apr 27, 2026
ce9cacb
Address AGENTS validation feedback
Copilot Apr 27, 2026
3f39e79
Adjustments + Fixes
Nickelony May 8, 2026
d3d2c41
Address Copilot comments
Nickelony May 8, 2026
158706b
Merge branch 'develop' into Nickelony/NEW-WPF
Nickelony May 16, 2026
e7fe805
Merge branch 'develop' into Nickelony/NEW-WPF
Lwmte May 17, 2026
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
116 changes: 77 additions & 39 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## General Project Information

- Language: **C# targeting .NET 8** (desktop application).
- Language: **C# targeting .NET 8** (desktop application).
- This is a level editor suite for a family of 3D game engines used in the classic Tomb Raider series.
- Level formats are grid-based, room-based, and portal-based.
- A room is a spatial container for level geometry and game entities.
Expand All @@ -10,21 +10,43 @@

## General Guidelines

### Files and Namespaces

- Files must use Windows line endings. Only standard ASCII symbols are allowed; do not use Unicode symbols.
- `using` directives are grouped and sorted as follows: `DarkUI` namespaces first, then `System` namespaces, followed by third-party and local namespaces.
- Namespace declarations and type definitions should place the opening brace on a new line.
- Prefer grouping all feature-related functionality within a self-contained module or modules. Avoid creating large code blocks over 10–15 lines in existing modules; instead, offload code to helper functions.
- Avoid duplicating and copypasting code. Implement helper methods instead, whenever similar code is used within a given module, class or feature scope.
- Every document should end with a trailing newline.
- `using` directives and namespace declarations should always be sorted alphabetically.
- Remove unused `using` statements.
- Prefer importing namespaces over fully qualifying framework types when there is no ambiguity. Remove redundant qualifiers such as `System.StringComparison` and use `StringComparison` directly when no namespace conflict exists.
- Prefer file-scoped namespaces where a file contains a single namespace and no language constraint prevents it. If a block-scoped namespace is still required, place the opening brace on a new line and sort multiple namespace declarations alphabetically.

### Nullability

- Each refactor should enable nullable reference types for the touched code. Add `#nullable enabled` at the top of the file only when the project does not already enable nullables, and update the touched code to use nullable annotations and checks correctly.
- Always use `is null` / `is not null` rather than `== null` / `!= null`.
- Prefer nullability attributes and helpers such as `[NotNullWhen]`, `[MemberNotNull]`, `[MaybeNullWhen]` and related annotations when they improve flow analysis and keep the API clear.
- Avoid the null-forgiving operator (`!`) where possible. Prefer flow analysis, null checks, annotations and helper methods instead, and use `!` only when it is truly necessary to express a proven invariant the compiler cannot infer.

### Architecture and Composition

- Keep feature-related functionality within self-contained modules. Avoid large code blocks over 10-15 lines in existing modules; move that logic into helpers or dedicated types.
- Always look for opportunities to de-duplicate code and fix duplication where suitable. Prefer shared helpers or extracted modules when similar code appears within a module, class or feature scope.
- Prefer modern .NET and C# conventions when project-specific guidance does not require something else.
- Design new code with service-based composition in mind. Favor dependency injection seams, and use the temporary `TombLib.WPF` service locator only in code paths that already depend on it.
- Keep vertical slice architecture in mind when choosing where new features, helpers and dependencies should live.

## Formatting

- **Indentation** is four spaces; tabs are not used.
### Indentation

- Indentation uses four spaces; tabs are not used.

- **Braces**:
- Always use braces for multi-statement blocks.
- Do not use braces for single-statement blocks, unless they are within multiple `else if` conditions where surrounding statements are multi-line.

- Opening curly brace `{` for structures, classes and methods should be on the next line, not on the same line:
### Braces

- Always use braces for multi-statement blocks.
- Single-line conditions should not use braces when the entire `if` / `else if` / `else` chain stays single-line.
- Multi-line conditions or multi-line bodies must always use braces.
- If any branch in an `if` / `else if` / `else` chain uses braces, all sibling branches should use braces as well.
- Opening curly brace `{` for structures, classes and methods should be on the next line, not on the same line:

```csharp
public class Foo
Expand All @@ -39,27 +61,29 @@
}
```

- Anonymous delegates and lambdas should keep the brace on the same line:
`delegate () { ... }` or `() => { ... }`.

- **Line breaks and spacing**:
- A blank line separates logically distinct groups of members (fields, constructors, public methods, private helpers, etc.).
- Spaces around binary operators (`=`, `+`, `==`, etc.) and after commas.
- A single space follows keyword `if`/`for`/`while` before the opening parenthesis.
- Expressions may be broken into multiple lines and aligned with the previous line's indentation level to improve readability.
- However, chained LINQ method calls, lambdas or function/method arguments should not be broken into multiple lines, unless they reach more than 150 symbols in length.

- Do not collapse early exits or single-statement conditions into a single line:

Bad example:
```csharp
if (condition) return;
```
Do this instead:
```csharp
if (condition)
return;
```
- Anonymous delegates and lambdas should keep the brace on the same line: `delegate () { ... }` or `() => { ... }`.

### Line Breaks and Spacing

- A blank line separates logically distinct groups of members (fields, constructors, public methods, private helpers, etc.).
- Within method bodies, use a blank line between logically distinct statements and before a control-flow block that starts a new step.
- Avoid whitespace-only lines or dead indentation; blank lines should be truly blank.
- Spaces around binary operators (`=`, `+`, `==`, etc.) and after commas.
- A single space follows keyword `if` / `for` / `while` before the opening parenthesis.
- Expressions may be broken into multiple lines and aligned with the previous line's indentation level to improve readability.
- Chained LINQ method calls, lambdas or function arguments should stay on one line unless they exceed roughly 150 characters.
- Do not collapse early exits or single-statement conditions into one line.

Bad example:
```csharp
if (condition) return;
```

Do this instead:
```csharp
if (condition)
return;
```

## Naming

Expand All @@ -75,40 +99,54 @@

- Fields are generally declared as `public` or `private readonly` depending on usage; expose state via properties where appropriate.
- `var` type should be preferred where possible, when the right-hand type is evident from the initializer.
- Explicit typing should be only used when it is required by logic or compiler, or when type name is shorter than 6 symbols (e.g. `int`, `bool`, `float`).
- Explicit typing should only be used when it is required by logic or compiler, or when the type name is shorter than 6 symbols (e.g. `int`, `bool`, `float`).
- For floating-point numbers, always use `f` postfix and decimal, even if value is not fractional (e.g. `2.0f`).
- Consider `record` or `record struct` when value semantics, immutability, or concise data-carrier behavior make them a better fit than a class or struct.
- Prefer expression-bodied members for methods or properties whose implementation is a single readable line.
- Prefer collection expressions (`[]`, `[item]`, `[..items]`) over `Array.Empty<T>()`, explicit array or list construction, or simple `.ToArray()` / `.ToList()` materialization when the target type supports them and the result stays clear.

## Control Flow and Syntax

- Avoid excessive condition nesting and use early exits / breaks where possible.
- LINQ and lambda expressions are used for collections (`FirstOrDefault`, `Where`, `Any`, etc.).
- Use pattern matching where it keeps the code clearer or removes redundant casts, temporary variables or branching.
- Under nullable-aware code, avoid throwing `ArgumentNullException` for non-nullable parameters when the guard adds no meaningful value.
- When an exception type exposes helper APIs such as `ArgumentNullException.ThrowIfNull`, prefer those helpers over manual `if` blocks when the behavior stays clear.
- Exception and error handling is done with `try`/`catch`, and caught exceptions are logged with [NLog](https://nlog-project.org/) where appropriate.
- Warnings must also be logged by NLog, if cause for the incorrect behaviour is user action.
- Warnings caused by user action should also be logged through NLog.

## Comments

- When comments appear they are single-line `//`. Block comments (`/* ... */`) are rare.
- Comments are sparse. Code relies on meaningful names rather than inline documentation.
- Do not use `<summary>` if surrounding code and/or module isn't already using it. Only add `<summary>` for non-private methods with high complexity.
- If module or function implements complex functionality, a brief description (2-3 lines) may be added in front of it, separated by a blank line from the function body.
- Add XML documentation to classes where it clarifies intent, and to public methods and public properties by default. Use XML documentation for private members only when the behavior is complex enough that names alone are not sufficient.
- If a module or function implements complex functionality, use brief section comments to split long methods into smaller, digestible steps.
- All descriptive comments should end with a full stop (`.`).

## Code Grouping

- Large methods should group related actions together, separated by blank lines.
- Large methods should group related actions together, separated by blank lines and short section comments when they cannot be broken apart further.
- Constants and static helpers that are used several times should appear at the top of a class.
- Constants that are used only within a scope of a method, should be declared within this method.
- One-liner lambdas may be grouped together, if they share similar meaning or functionality.
- Prefer one top-level type per file when practical. Keep multiple classes, enums, records or interfaces in the same file only when they are strictly coupled.
- When a class grows too large in size or scope, split it into smaller partial classes organized by responsibility. Use partial classes only when the responsibilities still belong to the same type; otherwise extract a dedicated helper, service or type instead.
- Avoid one-line wrapper methods unless they remove duplication, enforce a policy, or provide meaning beyond a direct redirect.
- Do not keep generic helper methods inside the same feature class. First check whether a suitable shared helper already exists elsewhere in the codebase; otherwise extract the helper into the most suitable shared library project or dedicated module.
- If a helper method is broad in scope, such as a general WPF helper like `FindAncestor()`, first verify whether an equivalent already exists. If not, place it in the narrowest suitable shared library among `TombLib`, `TombLib.Scripting`, `TombLib.WPF` and `DarkUI.WPF` rather than adding it to a feature-local helper class.

## User Interface Implementation

- For WinForms-based workflows, maintain the existing Visual Studio module pair for each control or unit: `.cs` and `.Designer.cs`.
- For existing WinForms-based `DarkUI` controls and containers, prefer to use existing WinForms-based `DarkUI` controls.
- For new controls and containers with complex logic, or where WinForms may not perform fast enough, prefer `DarkUI.WPF` framework. Use `GeometryIOSettingsWindow` as a reference.
- For new WPF views and view models, use `GeometryIOSettingsWindow` as the reference for structure, localization and service usage patterns.
- When writing WPF UI, prioritize localization and the existing localization infrastructure from `TombLib.WPF`.
- Creating new generic WPF controls should be delegated to `DarkUI.WPF`.
- For new controls and containers with complex logic, or where WinForms may not perform fast enough, prefer `DarkUI.WPF`.
- Use `CommunityToolkit` functionality where possible.

## Performance

- For 3D rendering controls, prefer more performant approaches and locally cache frequently used data within the function scope whenever possible.
- Avoid scenarios where bulk data updates may cause event floods, as the project relies heavily on event subscriptions across multiple controls and sub-controls.
- Use `Parallel` for bulk operations to maximize performance. Avoid using it in thread-unsafe contexts or when operating on serial data sets.
- Use `Parallel` for bulk operations to maximize performance. Avoid using it in thread-unsafe contexts or when operating on serial data sets.
22 changes: 22 additions & 0 deletions DarkUI/DarkUI.WPF/Converters/HtmlToUIColorConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Drawing;
using System.Globalization;
using System.Windows.Data;

namespace DarkUI.WPF.Converters;

public class HtmlToUIColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var colString = (string)value;
var col = ColorTranslator.FromHtml(colString);
return System.Windows.Media.Color.FromArgb(col.A, col.R, col.G, col.B);
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var col = (System.Windows.Media.Color)value;
return ColorTranslator.ToHtml(Color.FromArgb(col.A, col.R, col.G, col.B));
}
}
34 changes: 34 additions & 0 deletions DarkUI/DarkUI.WPF/Converters/VectorToBrushConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Globalization;
using System.Numerics;
using System.Windows.Data;
using System.Windows.Media;

namespace DarkUI.WPF.Converters;

public class VectorToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not Vector4 vec)
return Binding.DoNothing;

byte r = (byte)(Math.Clamp(vec.X, 0.0f, 1.0f) * 255.0f);
byte g = (byte)(Math.Clamp(vec.Y, 0.0f, 1.0f) * 255.0f);
byte b = (byte)(Math.Clamp(vec.Z, 0.0f, 1.0f) * 255.0f);
byte a = (byte)(Math.Clamp(vec.W, 0.0f, 1.0f) * 255.0f);

var brush = new SolidColorBrush(Color.FromArgb(a, r, g, b));
brush.Freeze();
return brush;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not SolidColorBrush brush)
return Binding.DoNothing;

Color col = brush.Color;
return new Vector4(col.R / 255.0f, col.G / 255.0f, col.B / 255.0f, col.A / 255.0f);
}
}
26 changes: 26 additions & 0 deletions DarkUI/DarkUI.WPF/Converters/VectorToUIColorConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Globalization;
using System.Numerics;
using System.Windows.Data;
using System.Windows.Media;

namespace DarkUI.WPF.Converters;

public class VectorToUIColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var vec = (Vector4)value;
byte r = (byte)(Math.Clamp(vec.X, 0.0f, 1.0f) * 255.0f);
byte g = (byte)(Math.Clamp(vec.Y, 0.0f, 1.0f) * 255.0f);
byte b = (byte)(Math.Clamp(vec.Z, 0.0f, 1.0f) * 255.0f);
byte a = (byte)(Math.Clamp(vec.W, 0.0f, 1.0f) * 255.0f);
return Color.FromArgb(a, r, g, b);
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var col = (Color)value;
return new Vector4(col.R / 255.0f, col.G / 255.0f, col.B / 255.0f, col.A / 255.0f);
}
}
20 changes: 19 additions & 1 deletion DarkUI/DarkUI.WPF/CustomControls/AutoCompleteBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class AutoCompleteBox : Control
public static readonly DependencyProperty IsSuggestionVisibleProperty;
public static readonly DependencyProperty ItemsSourceProperty;
public static readonly DependencyProperty SelectItemProperty;
public static readonly DependencyProperty CharacterCasingProperty;

public string Text
{
Expand All @@ -42,6 +43,12 @@ public ICommand SelectItem
set => SetValue(SelectItemProperty, value);
}

public CharacterCasing CharacterCasing
{
get => (CharacterCasing)GetValue(CharacterCasingProperty);
set => SetValue(CharacterCasingProperty, value);
}

public TextBox? InputTextBox { get; set; }
public Popup? Popup { get; set; }
public ListBox? SuggestionBox { get; set; }
Expand Down Expand Up @@ -71,6 +78,12 @@ static AutoCompleteBox()
typeof(ICommand),
typeof(AutoCompleteBox),
new PropertyMetadata(null));

CharacterCasingProperty = DependencyProperty.Register(
nameof(CharacterCasing),
typeof(CharacterCasing),
typeof(AutoCompleteBox),
new PropertyMetadata(CharacterCasing.Normal));
}

public override void OnApplyTemplate()
Expand Down Expand Up @@ -122,7 +135,12 @@ public override void OnApplyTemplate()
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == TextProperty && InputTextBox is not null)
InputTextBox.Text = (string)e.NewValue;
{
string newValue = (string)e.NewValue;

if (!string.Equals(InputTextBox.Text, newValue, StringComparison.Ordinal))
InputTextBox.Text = newValue;
}

base.OnPropertyChanged(e);
}
Expand Down
Loading