Rules and patterns for writing tests of Avalonia UI visual elements (controls, dialogs, panels). Use when creating or modifying tests for XAML controls, dialogs, or panels.
日本語の概要は準備中です。原文の説明を表示しています。
Rules and patterns for creating Avalonia UI visual elements (controls, dialogs, panels, windows). Use when creating or modifying XAML controls, dialogs, panels, or windows in this project.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Follow these rules strictly when creating or modifying any visual element (control, dialog, panel, window) in this project.
{DynamicResource AppFontSize} — NEVER hardcode font sizesx:FieldModifier="internal" for code-behind accessDataTemplate with ItemTemplate for displaying model objectsUse this structure for all dialogs:
<Window ...
Title="Edit [Element]"
Width="450" Height="380"
MinWidth="400" MinHeight="320"
FontSize="{DynamicResource AppFontSize}"
WindowStartupLocation="CenterOwner"
CanResize="true">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="20" Spacing="10">
<!-- Section header -->
<TextBlock Text="Section Title" FontWeight="SemiBold"
FontSize="{DynamicResource AppFontSize}"/>
<!-- Input grid: 100px label, * control -->
<Grid ColumnDefinitions="100,*" RowDefinitions="Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Label:"
VerticalAlignment="Center"
FontSize="{DynamicResource AppFontSize}"/>
<controls:MeasurementControl Grid.Row="0" Grid.Column="1"
x:Name="Control1" x:FieldModifier="internal"
FontSize="{DynamicResource AppFontSize}"/>
<!-- Subsequent rows get top margin -->
<TextBlock Grid.Row="1" Grid.Column="0" Text="Label:"
VerticalAlignment="Center"
FontSize="{DynamicResource AppFontSize}"
Margin="0,8,0,0"/>
<controls:MeasurementControl Grid.Row="1" Grid.Column="1"
x:Name="Control2" x:FieldModifier="internal"
FontSize="{DynamicResource AppFontSize}"
Margin="0,8,0,0"/>
</Grid>
<!-- Checkboxes align under controls, not labels -->
<Grid ColumnDefinitions="100,*">
<CheckBox Grid.Column="1" x:Name="SomeCheckBox"
x:FieldModifier="internal" Content="Option"
FontSize="{DynamicResource AppFontSize}"/>
</Grid>
<!-- Buttons: centered, horizontal, 10px spacing, 80px wide -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"
Spacing="10" Margin="0,20,0,0">
<Button Content="OK" Click="OnOK" Width="80"
FontSize="{DynamicResource AppFontSize}"/>
<Button Content="Cancel" Click="OnCancel" Width="80"
FontSize="{DynamicResource AppFontSize}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Window>
| Rule | Value |
|---|---|
| Label column width | 100px |
| Control column | * (fill remaining) |
| Row spacing | Margin="0,8,0,0" on subsequent rows |
| Content margin | Margin="20" on outer StackPanel |
| Content spacing | Spacing="10" on outer StackPanel |
| Section headers | FontWeight="SemiBold" |
| Button width | 80px |
| Button spacing | 10px |
| Button top margin | Margin="0,20,0,0" |
| Checkbox alignment | Under controls (Grid.Column="1"), not labels |
| Window startup | CenterOwner |
| Resizable | CanResize="true" with Min dimensions |
public partial class EditSomethingDialog : Window
{
private readonly SomeElement _element;
public EditSomethingDialog(SomeElement element)
{
_element = element ?? throw new ArgumentNullException(nameof(element));
InitializeComponent();
// Initialize controls
Control1.UnitType = typeof(SomeUnit);
PopulateFromElement();
// Window state persistence
Utilities.WindowStateManager.RestoreDialogSize(this, nameof(EditSomethingDialog));
Closing += (s, e) => Utilities.WindowStateManager.SaveDialogSize(this, nameof(EditSomethingDialog));
}
private void PopulateFromElement()
{
Control1.SetValue(_element.SomeProperty);
}
public void Save()
{
var value = Control1.GetValue<SomeUnit>();
if (value.HasValue) _element.SomeProperty = value.Value;
}
private void OnOK(object? sender, RoutedEventArgs e) { Save(); Close(); }
private void OnCancel(object? sender, RoutedEventArgs e) { Close(); }
}
Controllers/, thin UI wrapper in Controls/Changed event, let the application decide what to do// GET: always read from UI controls directly (no stored state)
public object? Value
{
get => _controller.Value(NumericPart?.Text ?? "", unit, DecimalPoints, Culture);
set
{
NumericPart.Text = text;
SelectUnit(unit);
}
}
// Events just notify — never update properties in handlers
NumericPart.TextChanged += (s, e) => Changed?.Invoke(this, EventArgs.Empty);
Type property + reflection insteadControllers are always created as part of a control — never standalone. They live in Controllers/ and contain ALL complex logic so the control code-behind stays thin.
using Avalonia.* anywhere[assembly: InternalsVisibleTo("ProjectName.Tests")] at top of fileProjectName.ControllersEvery input controller follows this method pattern (adapt names to your domain):
public class SomethingController // or SomethingController<T> where T : Enum
{
// Configuration properties with defaults
public double Increment { get; set; } = 1.0;
public double Minimum { get; set; } = -10000.0;
public double Maximum { get; set; } = 10000.0;
public int? DecimalPoints { get; set; } = null;
// 1. Enumerate options (for ComboBox population)
public IReadOnlyList<OptionType> GetOptions(out int defaultIndex) { }
// 2. Parse text → typed value (returns null on invalid/empty input)
public ValueType? Value(string text, ..., int? accuracy, CultureInfo? culture = null) { }
// 3. Format typed value → text + selection state (out parameters)
public void ParseValue(ValueType value, out string text, out OptionType option,
int? decimalPoints = null, CultureInfo? culture = null) { }
// 4. Increment/decrement (clamps to Min/Max, rounds to DecimalPoints)
public string IncrementValue(string currentText, bool increment,
CultureInfo? culture = null) { }
// 5. Character filter for real-time input validation
public bool AllowKeyInEditor(string currentText, int caretIndex,
int selectionLength, char character,
CultureInfo? culture = null) { }
}
Not every controller needs all five methods. Omit what the control doesn't need:
Value() and ParseValue()// Always accept optional culture, default to InvariantCulture
public ValueType? Value(string text, ..., CultureInfo? culture = null)
{
culture ??= CultureInfo.InvariantCulture;
// ...
}
// IncrementValue clamps to Min/Max
newValue = Math.Max(Minimum, Math.Min(Maximum, newValue));
// Value() does NOT reject out-of-range input
// Comment this explicitly:
// Note: Min/Max are only used for increment/decrement, not validation
When a controller is generic but XAML can't use generics:
Controller — generic, clean:
public class MeasurementController<T> where T : Enum
{
public Measurement<T>? Value(string text, T unit, int? accuracy, CultureInfo? culture = null) { }
}
Control — non-generic, uses Type property + reflection:
public partial class MeasurementControl : UserControl
{
private object? _controller; // MeasurementController<T> via reflection
private Type? _unitType;
private void InitializeController()
{
var controllerType = typeof(MeasurementController<>).MakeGenericType(_unitType);
_controller = Activator.CreateInstance(controllerType);
SyncPropertiesToController();
}
// Call controller methods via reflection
public object? Value
{
get
{
var valueMethod = _controller.GetType().GetMethod("Value");
return valueMethod?.Invoke(_controller, new object?[] { text, unit, DecimalPoints, Culture });
}
}
}
this.PropertyChanged += (s, e) =>
{
if (e.Property == IncrementProperty)
_controller?.GetType().GetProperty("Increment")?.SetValue(_controller, (double)e.NewValue!);
else if (e.Property == MinimumProperty)
_controller?.GetType().GetProperty("Minimum")?.SetValue(_controller, (double)e.NewValue!);
// ... etc
};
private void WireEvents()
{
this.GotFocus += (s, e) => NumericPart?.Focus();
if (NumericPart != null)
{
NumericPart.KeyDown += NumericPart_KeyDown;
NumericPart.AddHandler(TextInputEvent, NumericPart_TextInput, RoutingStrategies.Tunnel);
NumericPart.TextChanged += (s, e) => Changed?.Invoke(this, EventArgs.Empty);
}
if (UnitPart != null)
{
UnitPart.SelectionChanged += (s, e) => Changed?.Invoke(this, EventArgs.Empty);
}
}
private void NumericPart_KeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Up) { DoIncrement(true); e.Handled = true; }
if (e.Key == Key.Down) { DoIncrement(false); e.Handled = true; }
if (e.Key == Key.Tab) { UnitPart.Focus(); e.Handled = true; }
}
private void NumericPart_TextInput(object? sender, TextInputEventArgs e)
{
// Delegate to controller.AllowKeyInEditor() — reject invalid chars
var allowed = (bool)(allowKeyMethod?.Invoke(_controller, new object?[] {
NumericPart.Text, NumericPart.CaretIndex,
NumericPart.SelectionEnd - NumericPart.SelectionStart, c, Culture
}) ?? false);
if (!allowed) e.Handled = true;
}
Panels are desktop-only (not shared with mobile). They follow the same layout rules as dialogs but are embedded in the main window rather than standalone windows.
public MeasurementSystem MeasurementSystem { get; set; } // Switch units
public SomeDataType Data { get; set; } // Get/Set all values
public event EventHandler Changed; // Notify parent
Controls/ → XAML + code-behind (thin UI layer)
Controllers/ → Pure C# logic (no UI dependencies)
Models/ → Data models (UnitItem, DragTableInfo, etc.)
[AvaloniaFact] attribute<Application.Resources>
<x:Double x:Key="AppFontSize">13</x:Double>
</Application.Resources>
<Application.Styles>
<StyleInclude Source="avares://Classic.Avalonia.Theme/ClassicTheme.axaml"/>
<Style Selector=":is(TemplatedControl)">
<Setter Property="FontSize" Value="{DynamicResource AppFontSize}" />
</Style>
</Application.Styles>
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Rules and patterns for writing tests of Avalonia UI visual elements (controls, dialogs, panels). Use when creating or modifying tests for XAML controls, dialogs, or panels.
日本語の概要は準備中です。原文の説明を表示しています。
Use whenever writing, reviewing, or debugging .NET/C# code that uses the BallisticCalculator NuGet package (namespace BallisticCalculator) or its Gehtsoft.Measurements unit types — even when the user doesn't name the package explicitly but is clearly doing external/interior ballistics in .NET (bullet drop, velocity, energy, windage, spin drift, time of flight, zeroing a scope) for rifles, air rifles, bows, or artillery. Covers the full public API (Ammunition, Rifle, Atmosphere, Wind, ShotParameters, TrajectoryCalculator, TrajectoryPoint); standard, custom, .drg, and multi-BC drag tables; saving/loading data and building your own file format around it (BXml and System.Text.Json serialization); and building or rendering scope reticles (e.g. to SVG). Also use it for this library's Gehtsoft.Measurements Measurement<T> unit types (DistanceUnit, VelocityUnit, AngularUnit, WeightUnit, PressureUnit, …). Skip for: generic physics or projectile-motion exercises, a different ballistics library (e.g. GNU/JBM), game-engine or Unity projectile motion, plain unit conversions, and non-code ballistics questions (load-data or forensics advice). Self-contained: no access to the library source or binaries is required — prefer this over scanning the package to rediscover the API. Describes 1.1.13+, including the named ZeroRangeCantBeReachedException / TrajectoryCannotBeCalculatedException failures.
日本語の概要は準備中です。原文の説明を表示しています。
Author and modify documentation for projects that use Gehtsoft's docgen utility. Use this skill whenever the user mentions docgen, .ds files, the doc/ directory of a repository, project.xml or project.proj for documentation, or wants to write/edit/build/debug API documentation in any codebase that uses docgen. Also use it whenever the user asks about generating C#, C++, or Java API documentation that follows Gehtsoft conventions, even if they don't say "docgen" explicitly.
日本語の概要は準備中です。原文の説明を表示しています。
Design a rifle-scope reticle from a plain-language description and emit it as a BallisticCalculator `.reticle` file (BXml) plus a rendered SVG preview. Covers the complete `.reticle` element/attribute specification, reticle design principles (mrad vs MOA subtension, FFP/SFP, stroke-weight hierarchy, hash-mark ladders, christmas-tree wind holds, clutter budget, BDC anchors) and worked, verified patterns for duplex, mil-dot, hash-ladder, christmas-tree, chevron, circle-dot, dense-grid and rangefinding-stadia reticles. Also covers reproducing an existing commercial reticle from its manufacturer's technical data sheet. Use this skill whenever the user wants to create, modify, review or render a scope reticle, crosshair, mil-dot / MOA grid, holdover ladder, christmas tree, BDC ladder or rangefinding stadia — including any mention of a `.reticle` file, `ReticleDefinition`, `ReticleDrawController`, `SvgCanvasFactory`, or "draw me a reticle", and including requests to clone or model a named commercial reticle (Horus H58/H59, TReMoR, Mil-C, MOAR, Tremor3, EBR, and the like). Trigger it even when the user only says something like "make a crosshair with 0.2 mil hashes out to 5 mils" or "turn this reticle picture into a file", and trigger it before hand-writing reticle XML or hand-rolling reticle geometry, because the file format silently ignores misspelled attributes and mis-cased booleans.
日本語の概要は準備中です。原文の説明を表示しています。