Compare commits

..

5 Commits

Author SHA1 Message Date
maj00r
5af567be7f Merge pull request #40 from MaSzyna-EU07/feature/blackbox
Black box player: the simulator's recording of a run on a map, with the cab, the timetable and a summary
2026-11-12 17:44:02 +01:00
maj00r
b201b6f7ab Merge pull request #42 from MaSzyna-EU07/fix/consist-strip-selection
fix(scenarios): frame the vehicle started in, no background line between vehicles
2026-11-12 17:43:46 +01:00
maj00r
3a19fd5da2 Merge pull request #38 from MaSzyna-EU07/feat/sonarcloud-analysis
ci: analyse with SonarQube Cloud
2026-11-11 20:16:17 +01:00
maj00r
36df14abee fix(scenarios): the vehicle started in is framed, and no line of the background shows between two vehicles at a scale such as 125 % 2026-11-11 19:01:57 +01:00
jerrrrycho
e5330d91a6 ci: analyse with SonarQube Cloud
Runs SonarScanner for .NET around a full build and the tests, with
OpenCover coverage from coverlet, on pushes to main and on pull requests
from branches of this repository. Fork and Dependabot pull requests are
skipped: the C# analysis runs inside the build, so the token and the
built code cannot be kept on separate machines as in maszyna.

The scanner is pinned in .config/dotnet-tools.json for Dependabot.
2026-11-07 06:27:16 +01:00
3 changed files with 149 additions and 2 deletions

13
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-sonarscanner": {
"version": "12.1.0",
"commands": [
"dotnet-sonarscanner"
],
"rollForward": true
}
}
}

114
.github/workflows/sonar.yml vendored Normal file
View File

@@ -0,0 +1,114 @@
# .github/workflows/sonar.yml
# Has SONAR_TOKEN, so it only ever builds code that is already trusted: pushes to
# main and pull requests opened from a branch of this repository.
#
# Unlike the C++ analysis of maszyna, this one cannot be split into an untrusted
# build and a trusted scan. The C# analyzers run inside the compiler, and the
# scanner's begin step, which needs the token, has to run before the build on the
# same machine. A fork's build could leave a process behind to read the token from
# the end step, so fork pull requests are not analysed here. GitHub withholds the
# secret from them (and from Dependabot) anyway.
#
# The scanner is pinned in .config/dotnet-tools.json, where Dependabot updates it.
# SonarScanner for .NET takes its settings on the command line only; it refuses
# to run next to a sonar-project.properties, so there is none.
name: SonarQube Cloud
on:
# Main branch only: analysing other branches is a paid feature, pull requests
# are not.
push:
branches:
- main
pull_request:
branches:
- main
- staging
types: [ opened, reopened, synchronize ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: sonar-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_NOLOGO: true
SOLUTION_PATH: StarterNG.sln
TEST_PROJECT_PATH: StarterNG.Tests/StarterNG.Tests.csproj
jobs:
sonar:
name: Analyze
if: >
github.event_name != 'pull_request' ||
(github.event.pull_request.head.repo.full_name == github.repository &&
github.actor != 'dependabot[bot]')
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
# Full history, so new code and blame are told apart correctly.
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Setup .NET 9
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: 9.0.x
- name: Restore tools
run: dotnet tool restore
# sonar.qualitygate.wait=true blocks the end step until the server has
# processed the report, so the job's result is the quality gate's.
- name: Begin analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: >
dotnet tool run dotnet-sonarscanner begin
/k:"MaSzyna-EU07_StarterNG"
/o:"maszyna-eu07"
/n:"StarterNG"
/d:sonar.host.url="https://sonarcloud.io"
/d:sonar.token="$SONAR_TOKEN"
/d:sonar.scm.revision="${{ github.event.pull_request.head.sha || github.sha }}"
/d:sonar.cs.opencover.reportsPaths="TestResults/**/coverage.opencover.xml"
/d:sonar.cs.vstest.reportsPaths="TestResults/*.trx"
/d:sonar.qualitygate.wait=true
# A full, non-incremental build: the analyzers only see what is compiled.
- name: Build
run: >
dotnet build "${{ env.SOLUTION_PATH }}"
-c Release
--no-incremental
# Coverlet is already referenced by the test project; OpenCover is the
# format the C# analyzer reads.
- name: Test with coverage
run: >
dotnet test "${{ env.TEST_PROJECT_PATH }}"
-c Release
--no-build
--results-directory TestResults
--logger trx
--collect "XPlat Code Coverage"
--
DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
- name: End analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: >
dotnet tool run dotnet-sonarscanner end
/d:sonar.token="$SONAR_TOKEN"

View File

@@ -1009,12 +1009,14 @@ public partial class Scenarios : UserControl
int thumbH = AppServices.Current.Settings.LargeThumbnails ? 64 : 32; int thumbH = AppServices.Current.Settings.LargeThumbnails ? 64 : 32;
var bmp = LoadMini(miniName, thumbH); var bmp = LoadMini(miniName, thumbH);
// Fill, not Uniform: at a scale such as 125 % layout rounds the slot up to whole pixels,
// and a mini keeping its own width inside it left a line of the background between two vehicles.
Control visual = bmp is not null Control visual = bmp is not null
? MiniTextures.Sharp(new Image ? MiniTextures.Sharp(new Image
{ {
Source = bmp, Source = bmp,
Height = thumbH, Height = thumbH,
Stretch = Avalonia.Media.Stretch.Uniform, Stretch = Avalonia.Media.Stretch.Fill,
Margin = new Thickness(0) Margin = new Thickness(0)
}) })
: MiniTextures.MissingVisual(train.SkinFile, thumbH); : MiniTextures.MissingVisual(train.SkinFile, thumbH);
@@ -1022,6 +1024,22 @@ public partial class Scenarios : UserControl
bool selected = !string.IsNullOrEmpty(train.Name) && bool selected = !string.IsNullOrEmpty(train.Name) &&
string.Equals(train.Name, AppServices.Current.State.StartingVehicleName, string.Equals(train.Name, AppServices.Current.State.StartingVehicleName,
StringComparison.OrdinalIgnoreCase); StringComparison.OrdinalIgnoreCase);
// The vehicle started in gets a frame over its picture as well as the lines above and below:
// two thin lines alone were hard to see. Drawn over the picture, it moves no vehicle aside.
if (selected)
visual = new Panel
{
Children =
{
visual,
new Border
{
BorderThickness = new Thickness(3),
BorderBrush = SelectedVehicleBrush,
IsHitTestVisible = false
}
}
};
var slot = new Border var slot = new Border
{ {
@@ -1210,7 +1228,9 @@ public partial class Scenarios : UserControl
var source = consistStack.Children[i]; var source = consistStack.Children[i];
// The ghost mirrors what the slot shows, stand-in wagons included, so // The ghost mirrors what the slot shows, stand-in wagons included, so
// dragging a vehicle with no thumbnail still carries something visible. // dragging a vehicle with no thumbnail still carries something visible.
Control copy = source is Border { Child: Image image } // The picked vehicle's picture sits under its frame.
var shown = source is Border { Child: Panel { Children: [Image framed, ..] } } ? framed : (source as Border)?.Child;
Control copy = shown is Image image
? new Image { Source = image.Source, Height = image.Height, Stretch = image.Stretch } ? new Image { Source = image.Source, Height = image.Height, Stretch = image.Stretch }
: MiniTextures.MissingVisual(null, (int)source.Bounds.Height); : MiniTextures.MissingVisual(null, (int)source.Bounds.Height);
panel.Children.Add(copy); panel.Children.Add(copy);