Skip to content

Commit 6139682

Browse files
Merge pull request #1009 from anton-makarevich/opencode/issue940-20260528170554
3 phases done, 3292 tests pass, 14 files changed.
2 parents f9ba6e0 + abae315 commit 6139682

24 files changed

Lines changed: 655 additions & 1080 deletions

.agents/skills/sanet-mvvm/SKILL.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ public class MyViewModel : BaseViewModel
191191
| Member | Purpose |
192192
|---|---|
193193
| `NavigationService` | Lazy `INavigationService`; throws if not injected |
194+
| `SetNavigationService(INavigationService)` | Injects the navigation service (required for VMs not navigated to through the framework, e.g. modal VMs created with `new`) |
194195
| `IsBusy` | Bindable busy flag |
195196
| `ExpectsResult` | Set by framework for modal VMs |
196197
| `BackCommand` | Calls `NavigateBackAsync` |
@@ -348,6 +349,19 @@ if (criteria != null)
348349
- Do **not** use `BackCommand` or `NavigateBackAsync` inside modal VMs; close via `CloseAsync()`.
349350
- The caller's `await` unblocks as soon as `GetResultAsync()` completes.
350351

352+
### 7c — Sub-navigation from a modal VM
353+
354+
`ShowViewModelForResultAsync` does **not** inject `NavigationService` into the target VM. If a modal VM needs to show its own sub-dialogs (e.g. an info popup triggered from within the modal), the parent must call `SetNavigationService` before showing the modal:
355+
356+
```csharp
357+
// Parent VM (has NavigationService from navigation framework)
358+
var modalVm = new MyModalViewModel(data, service);
359+
modalVm.SetNavigationService(NavigationService); // required for sub-navigation
360+
await NavigationService.ShowViewModelForResultAsync<MyModalViewModel, ResultType>(modalVm);
361+
```
362+
363+
> ⚠️ **Missing `SetNavigationService`** → the modal VM's `NavigationService` property will throw `ArgumentNullException` on first access.
364+
351365
---
352366

353367
## §8 — Action Dialogs (Built-in)
@@ -449,3 +463,4 @@ When adding a screen, do **all** of these steps:
449463
| Forget to register view/VM pair | Always add to `RegisterViews` |
450464
| Use `NavigateToViewModelAsync` for modal/popup dialogs | Use `ShowViewModelForResultAsync` |
451465
| Resolve `NavigationService` in constructor | Access it lazily via the `NavigationService` property |
466+
| Use `NavigationService` in a modal VM without calling `SetNavigationService` first | Parent calls `modalVm.SetNavigationService(NavigationService)` before `ShowViewModelForResultAsync` |

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project>
33
<PropertyGroup>
4-
<Version>0.57.17</Version>
4+
<Version>0.57.18</Version>
55
<Authors>Anton Makarevich</Authors>
66
<Company>Anton Makarevich</Company>
77
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>

src/MakaMek.Avalonia/MakaMek.Avalonia/App.axaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ private void RegisterViews(INavigationService navigationService)
132132
navigationService.RegisterViews(typeof(AboutView), typeof(AboutViewModel));
133133
navigationService.RegisterViews(typeof(SettingsView), typeof(SettingsViewModel));
134134
navigationService.RegisterViews(typeof(AvailableUnitsTableView), typeof(AvailableUnitsTableViewModel));
135+
navigationService.RegisterViews(typeof(UnitInfoView), typeof(UnitInfoViewModel));
135136
}
136137
private bool IsMobile()
137138
{

src/MakaMek.Avalonia/MakaMek.Avalonia/Controls/UnitItemControl.axaml

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,27 @@
4646
HorizontalAlignment="Center"/>
4747
</StackPanel>
4848

49+
<!-- Info Button in Top Left Corner -->
50+
<Button
51+
Command="{Binding #UnitItemUserControl.InfoCommand}"
52+
CommandParameter="{Binding #UnitItemUserControl.InfoCommandParameter}"
53+
ToolTip.Tip="{extensions:Localize 'UnitItem_Info'}"
54+
Classes="cornerBadge"
55+
HorizontalAlignment="Left"
56+
Margin="-2,-2,0,0">
57+
<PathIcon Data="{StaticResource RecordSheetIcon}"/>
58+
</Button>
59+
4960
<!-- Remove Button in Top Right Corner -->
5061
<Button
5162
Command="{Binding #UnitItemUserControl.RemoveCommand}"
5263
CommandParameter="{Binding #UnitItemUserControl.RemoveCommandParameter}"
5364
IsVisible="{Binding #UnitItemUserControl.IsRemoveButtonVisible}"
5465
ToolTip.Tip="{extensions:Localize 'UnitItem_RemoveUnit'}"
55-
Classes="icon"
56-
Width="24"
57-
Height="24"
58-
Padding="2"
66+
Classes="cornerBadge"
5967
HorizontalAlignment="Right"
60-
VerticalAlignment="Top"
61-
Margin="0,-2,-2,0"
62-
Background="{StaticResource SecondaryBrush}">
63-
<TextBlock FontFamily="{StaticResource AwesomeFontSolid}"
64-
Text="&#xf2ed;"
65-
FontSize="10"
66-
Foreground="{StaticResource HighlightLightBrush}"/>
68+
Margin="0,-2,-2,0">
69+
<TextBlock Text="&#xf2ed;"/>
6770
</Button>
6871
</Grid>
6972
</Border>

src/MakaMek.Avalonia/MakaMek.Avalonia/Controls/UnitItemControl.axaml.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,27 @@ public partial class UnitItemControl : UserControl
2626
public static readonly StyledProperty<bool> IsRemoveButtonVisibleProperty =
2727
AvaloniaProperty.Register<UnitItemControl, bool>(nameof(IsRemoveButtonVisible), defaultValue: false);
2828

29+
public static readonly StyledProperty<ICommand?> InfoCommandProperty =
30+
AvaloniaProperty.Register<UnitItemControl, ICommand?>(nameof(InfoCommand));
31+
32+
public static readonly StyledProperty<object?> InfoCommandParameterProperty =
33+
AvaloniaProperty.Register<UnitItemControl, object?>(nameof(InfoCommandParameter));
34+
2935
public static readonly StyledProperty<string?> PlayerTintProperty =
3036
AvaloniaProperty.Register<UnitItemControl, string?>(nameof(PlayerTint));
3137

38+
public ICommand? InfoCommand
39+
{
40+
get => GetValue(InfoCommandProperty);
41+
set => SetValue(InfoCommandProperty, value);
42+
}
43+
44+
public object? InfoCommandParameter
45+
{
46+
get => GetValue(InfoCommandParameterProperty);
47+
set => SetValue(InfoCommandParameterProperty, value);
48+
}
49+
3250
public ICommand? RemoveCommand
3351
{
3452
get => GetValue(RemoveCommandProperty);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:controls="clr-namespace:Sanet.MakaMek.Avalonia.Controls"
6+
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="400"
7+
x:Class="Sanet.MakaMek.Avalonia.Controls.UnitRecordSheet"
8+
x:Name="UnitRecordSheetControl">
9+
<TabControl>
10+
<TabItem>
11+
<TabItem.Header>
12+
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf05a;"/>
13+
</TabItem.Header>
14+
<StackPanel Margin="10">
15+
<controls:UnitBasicInfoPanel DataContext="{Binding #UnitRecordSheetControl.Unit}" />
16+
<controls:UnitHeatLevelPanel DataContext="{Binding #UnitRecordSheetControl.HeatProjection}" IsVisible="{Binding #UnitRecordSheetControl.ShowHeatLevelPanel}" />
17+
<controls:UnitMovementInfoPanel DataContext="{Binding #UnitRecordSheetControl.Unit}" />
18+
</StackPanel>
19+
</TabItem>
20+
<TabItem IsVisible="{Binding #UnitRecordSheetControl.HasPilot}">
21+
<TabItem.Header>
22+
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf007;"/>
23+
</TabItem.Header>
24+
<controls:UnitPilotInfoPanel DataContext="{Binding #UnitRecordSheetControl.Unit.Pilot}" />
25+
</TabItem>
26+
<TabItem>
27+
<TabItem.Header>
28+
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf132;"/>
29+
</TabItem.Header>
30+
<controls:UnitHealthIndicator Unit="{Binding #UnitRecordSheetControl.Unit}"/>
31+
</TabItem>
32+
<TabItem>
33+
<TabItem.Header>
34+
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#x26a1;"/>
35+
</TabItem.Header>
36+
<controls:UnitWeaponsPanel Unit="{Binding #UnitRecordSheetControl.Unit}"/>
37+
</TabItem>
38+
<TabItem>
39+
<TabItem.Header>
40+
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf085;"/>
41+
</TabItem.Header>
42+
<controls:UnitComponentsPanel Unit="{Binding #UnitRecordSheetControl.Unit}" />
43+
</TabItem>
44+
<TabItem IsVisible="{Binding #UnitRecordSheetControl.ShowEventsTab}">
45+
<TabItem.Header>
46+
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf0e0;"/>
47+
</TabItem.Header>
48+
<controls:UnitEventsPanel/>
49+
</TabItem>
50+
</TabControl>
51+
</UserControl>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using Avalonia;
2+
using Avalonia.Controls;
3+
using Sanet.MakaMek.Core.Models.Units;
4+
5+
namespace Sanet.MakaMek.Avalonia.Controls;
6+
7+
public partial class UnitRecordSheet : UserControl
8+
{
9+
public static readonly StyledProperty<Unit?> UnitProperty =
10+
AvaloniaProperty.Register<UnitRecordSheet, Unit?>(nameof(Unit));
11+
12+
public static readonly StyledProperty<bool> HasPilotProperty =
13+
AvaloniaProperty.Register<UnitRecordSheet, bool>(nameof(HasPilot));
14+
15+
static UnitRecordSheet()
16+
{
17+
UnitProperty.Changed.AddClassHandler<UnitRecordSheet>((sender, _) => sender.OnUnitChanged());
18+
}
19+
20+
public static readonly StyledProperty<bool> ShowHeatLevelPanelProperty =
21+
AvaloniaProperty.Register<UnitRecordSheet, bool>(nameof(ShowHeatLevelPanel));
22+
23+
public static readonly StyledProperty<bool> ShowEventsTabProperty =
24+
AvaloniaProperty.Register<UnitRecordSheet, bool>(nameof(ShowEventsTab));
25+
26+
public static readonly StyledProperty<object?> HeatProjectionProperty =
27+
AvaloniaProperty.Register<UnitRecordSheet, object?>(nameof(HeatProjection));
28+
29+
public Unit? Unit
30+
{
31+
get => GetValue(UnitProperty);
32+
set => SetValue(UnitProperty, value);
33+
}
34+
35+
public bool HasPilot
36+
{
37+
get => GetValue(HasPilotProperty);
38+
set => SetValue(HasPilotProperty, value);
39+
}
40+
41+
public bool ShowHeatLevelPanel
42+
{
43+
get => GetValue(ShowHeatLevelPanelProperty);
44+
set => SetValue(ShowHeatLevelPanelProperty, value);
45+
}
46+
47+
public bool ShowEventsTab
48+
{
49+
get => GetValue(ShowEventsTabProperty);
50+
set => SetValue(ShowEventsTabProperty, value);
51+
}
52+
53+
public object? HeatProjection
54+
{
55+
get => GetValue(HeatProjectionProperty);
56+
set => SetValue(HeatProjectionProperty, value);
57+
}
58+
59+
public UnitRecordSheet()
60+
{
61+
InitializeComponent();
62+
}
63+
64+
private void OnUnitChanged()
65+
{
66+
HasPilot = Unit?.Pilot is not null;
67+
}
68+
}

src/MakaMek.Avalonia/MakaMek.Avalonia/Styles/Buttons.axaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,35 @@
146146
<Style Selector="Button.cardAction:pointerover">
147147
<Setter Property="Opacity" Value="0.7"/>
148148
</Style>
149+
150+
<!-- Corner Badge Button (24x24 icon badge for card corners) -->
151+
<Style Selector="Button.cornerBadge">
152+
<Setter Property="Width" Value="24"/>
153+
<Setter Property="Height" Value="24"/>
154+
<Setter Property="Padding" Value="2"/>
155+
<Setter Property="VerticalAlignment" Value="Top"/>
156+
<Setter Property="Background" Value="{StaticResource SecondaryBrush}"/>
157+
<Setter Property="Foreground" Value="{StaticResource HighlightLightBrush}"/>
158+
<Setter Property="FontFamily" Value="{StaticResource AwesomeFontSolid}"/>
159+
<Setter Property="HorizontalContentAlignment" Value="Center"/>
160+
<Setter Property="VerticalContentAlignment" Value="Center"/>
161+
</Style>
162+
163+
<Style Selector="Button.cornerBadge:pointerover">
164+
<Setter Property="Background" Value="{StaticResource SecondaryDarkBrush}"/>
165+
</Style>
166+
167+
<Style Selector="Button.cornerBadge:pressed">
168+
<Setter Property="Background" Value="{StaticResource SecondaryDarkBrush}"/>
169+
<Setter Property="Opacity" Value="0.8"/>
170+
</Style>
171+
172+
<!-- Inline Icon Button (small FontAwesome icon inline with text labels) -->
173+
<Style Selector="Button.inlineIcon">
174+
<Setter Property="Padding" Value="5,2"/>
175+
<Setter Property="FontFamily" Value="{StaticResource AwesomeFontSolid}"/>
176+
<Setter Property="HorizontalContentAlignment" Value="Center"/>
177+
<Setter Property="VerticalContentAlignment" Value="Center"/>
178+
</Style>
149179
</Styles>
150180

src/MakaMek.Avalonia/MakaMek.Avalonia/Views/AvailableUnitsTableView.axaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
33
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
44
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5-
xmlns:wrappers="clr-namespace:Sanet.MakaMek.Presentation.ViewModels.Wrappers;assembly=Sanet.MakaMek.Presentation"
65
xmlns:templatedControls="clr-namespace:Sanet.MakaMek.Avalonia.Views.TemplatedControls"
76
xmlns:viewModels="clr-namespace:Sanet.MakaMek.Presentation.ViewModels;assembly=Sanet.MakaMek.Presentation"
87
xmlns:extensions="clr-namespace:Sanet.MakaMek.Avalonia.Controls.Extensions;assembly=Sanet.MakaMek.Avalonia.Controls"
@@ -141,9 +140,13 @@
141140
Spacing="10"
142141
HorizontalAlignment="Right"
143142
Margin="0,10,0,0">
143+
<Button IsVisible="{Binding CanShowUnitInfo}"
144+
Content="{extensions:Localize 'AvailableUnits_UnitInfo'}"
145+
Command="{Binding ShowUnitInfoCommand}"
146+
Classes="secondary"/>
144147
<Button Content="{extensions:Localize 'AvailableUnits_Cancel'}"
145148
Command="{Binding CancelCommand}"
146-
Classes="secondary"/>
149+
Classes="primary"/>
147150
<Button Content="{extensions:Localize 'AvailableUnits_AddUnit'}"
148151
Command="{Binding AddUnitCommand}"
149152
IsEnabled="{Binding CanAddUnit}"

src/MakaMek.Avalonia/MakaMek.Avalonia/Views/BattleMapView.axaml

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -235,48 +235,11 @@
235235
IsVisible="{Binding IsRecordSheetPanelVisible}"
236236
Title="{Binding SelectedUnit.Name}"
237237
CloseCommand="{Binding ToggleRecordSheet}">
238-
<TabControl>
239-
<TabItem>
240-
<TabItem.Header>
241-
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf05a;"/>
242-
</TabItem.Header>
243-
<StackPanel Margin="10">
244-
<controls:UnitBasicInfoPanel DataContext="{Binding SelectedUnit}" />
245-
<controls:UnitHeatLevelPanel DataContext="{Binding SelectedUnitHeatProjection}" />
246-
<controls:UnitMovementInfoPanel DataContext="{Binding SelectedUnit}" />
247-
</StackPanel>
248-
</TabItem>
249-
<TabItem>
250-
<TabItem.Header>
251-
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf007;"/>
252-
</TabItem.Header>
253-
<controls:UnitPilotInfoPanel DataContext="{Binding SelectedUnit.Pilot}" />
254-
</TabItem>
255-
<TabItem>
256-
<TabItem.Header>
257-
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf132;"/>
258-
</TabItem.Header>
259-
<controls:UnitHealthIndicator Unit="{Binding SelectedUnit}"/>
260-
</TabItem>
261-
<TabItem>
262-
<TabItem.Header>
263-
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#x26a1;"/>
264-
</TabItem.Header>
265-
<controls:UnitWeaponsPanel Unit="{Binding SelectedUnit}"/>
266-
</TabItem>
267-
<TabItem>
268-
<TabItem.Header>
269-
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf085;"/>
270-
</TabItem.Header>
271-
<controls:UnitComponentsPanel Unit="{Binding SelectedUnit}" />
272-
</TabItem>
273-
<TabItem>
274-
<TabItem.Header>
275-
<TextBlock Classes="h3" FontFamily="{StaticResource AwesomeFontSolid}" Text="&#xf0e0;"/>
276-
</TabItem.Header>
277-
<controls:UnitEventsPanel/>
278-
</TabItem>
279-
</TabControl>
238+
<controls:UnitRecordSheet
239+
Unit="{Binding SelectedUnit}"
240+
HeatProjection="{Binding SelectedUnitHeatProjection}"
241+
ShowHeatLevelPanel="True"
242+
ShowEventsTab="True" />
280243
</templatedControls:GamePanel>
281244

282245
<!-- Command Log Panel -->

0 commit comments

Comments
 (0)