feat: 初始提交——一键运行快捷轮盘工具(WPF + C#)

This commit is contained in:
2026-08-30 15:44:15 +08:00
commit 2081a938bf
63 changed files with 4214 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
# 验证轮盘中心文本居中:标题像素包围盒应关于画布中心 (268,268) 对称,且状态间完全一致
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
function Measure-Hub($path, [string]$mode) {
$bmp = [System.Drawing.Bitmap]::FromFile((Resolve-Path $path))
$cx = 268; $cy = 268; $hubR = 54
$darkN = 0; $darkX = 0.0; $darkY = 0.0
$darkMinX = 9999; $darkMaxX = -1; $darkMinY = 9999; $darkMaxY = -1
$grayN = 0; $grayX = 0.0; $grayY = 0.0
for ($y = $cy - $hubR; $y -le $cy + $hubR; $y++) {
for ($x = $cx - $hubR; $x -le $cx + $hubR; $x++) {
$dx = $x - $cx; $dy = $y - $cy
if ($dx * $dx + $dy * $dy -gt $hubR * $hubR) { continue }
$px = $bmp.GetPixel($x, $y)
if ($px.A -lt 200) { continue }
$sum = $px.R + $px.G + $px.B
if ($mode -eq 'light') {
if ($sum -lt 330) {
$darkN++; $darkX += $x; $darkY += $y
if ($x -lt $darkMinX) { $darkMinX = $x }; if ($x -gt $darkMaxX) { $darkMaxX = $x }
if ($y -lt $darkMinY) { $darkMinY = $y }; if ($y -gt $darkMaxY) { $darkMaxY = $y }
}
elseif ($sum -ge 300 -and $sum -le 420) { $grayN++; $grayX += $x; $grayY += $y }
} else {
if ($sum -gt 560) {
$darkN++; $darkX += $x; $darkY += $y
if ($x -lt $darkMinX) { $darkMinX = $x }; if ($x -gt $darkMaxX) { $darkMaxX = $x }
if ($y -lt $darkMinY) { $darkMinY = $y }; if ($y -gt $darkMaxY) { $darkMaxY = $y }
}
elseif ($sum -ge 430 -and $sum -le 560) { $grayN++; $grayX += $x; $grayY += $y }
}
}
}
$bmp.Dispose()
$bboxCX = if ($darkN -gt 0) { ($darkMinX + $darkMaxX) / 2.0 } else { -1 }
Write-Output (" TitlePixelCount={0} BboxX=[{1},{2}] BboxCenterX={3} (期望268) BboxY=[{4},{5}]" -f $darkN, $darkMinX, $darkMaxX, [Math]::Round($bboxCX, 2), $darkMinY, $darkMaxY)
Write-Output (" HintPixelCount={0} HintCenterX={1} (供参考)" -f $grayN, $(if ($grayN -gt 0) { [Math]::Round($grayX / $grayN, 2) } else { -1 }))
}
foreach ($file in 'wheel-light.png', 'wheel-light-hover.png') {
Write-Output "=== $file ==="
Measure-Hub "artifacts\$file" 'light'
}
foreach ($file in 'wheel-dark.png', 'wheel-dark-hover.png') {
Write-Output "=== $file ==="
Measure-Hub "artifacts\$file" 'dark'
}
+70
View File
@@ -0,0 +1,70 @@
# 验证扇形文字走向:水平文字 → 白色水平笔画段占优;旧径向布局 → 对角笔画段占优
# 并校验图标位置:新布局图标在文字上方(旧布局图标朝外侧/下方)
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$bmp = [System.Drawing.Bitmap]::FromFile((Resolve-Path 'artifacts\wheel-light.png'))
# 3 项配置:扇形中点 -30°/90°/210°,中半径 145
$regions = @(
@{ Name = '扇形0(右上, 旧布局=对角60°)'; X = 328; Y = 168; W = 132; H = 57 },
@{ Name = '扇形1(下方, 旧布局=倒置水平)'; X = 204; Y = 385; W = 128; H = 55 },
@{ Name = '扇形2(左侧, 旧布局=对角-60°)'; X = 77; Y = 168; W = 132; H = 57 }
)
function Is-White($px) { return $px.A -gt 200 -and ($px.R + $px.G + $px.B) -gt 700 }
foreach ($r in $regions) {
$x0 = $r.X; $y0 = $r.Y; $w = $r.W; $h = $r.H
$white = New-Object 'bool[,]' ($h + 2), ($w + 2)
$topN = 0; $midN = 0; $botN = 0
for ($y = 0; $y -lt $h; $y++) {
for ($x = 0; $x -lt $w; $x++) {
$isWhite = Is-White ($bmp.GetPixel($x0 + $x, $y0 + $y))
$white[$y, $x] = $isWhite
if ($isWhite) {
if ($y -lt [int]($h / 3)) { $topN++ }
elseif ($y -lt [int](2 * $h / 3)) { $midN++ }
else { $botN++ }
}
}
}
$hRuns = 0; $vRuns = 0; $d1Runs = 0; $d2Runs = 0
for ($y = 0; $y -lt $h; $y++) {
$len = 0
for ($x = 0; $x -le $w; $x++) {
if ($x -lt $w -and $white[$y, $x]) { $len++ }
else { if ($len -ge 4) { $hRuns++ }; $len = 0 }
}
}
for ($x = 0; $x -lt $w; $x++) {
$len = 0
for ($y = 0; $y -le $h; $y++) {
if ($y -lt $h -and $white[$y, $x]) { $len++ }
else { if ($len -ge 4) { $vRuns++ }; $len = 0 }
}
}
for ($s = 0; $s -lt ($w + $h - 1); $s++) {
$len = 0
for ($y = 0; $y -lt $h; $y++) {
$x = $s - $y
if ($x -ge 0 -and $x -lt $w) {
if ($white[$y, $x]) { $len++ } else { if ($len -ge 4) { $d1Runs++ }; $len = 0 }
}
}
if ($len -ge 4) { $d1Runs++ }
}
for ($s = -($h - 1); $s -lt $w; $s++) {
$len = 0
for ($x = 0; $x -lt $w; $x++) {
$y = $x - $s
if ($y -ge 0 -and $y -lt $h) {
if ($white[$y, $x]) { $len++ } else { if ($len -ge 4) { $d2Runs++ }; $len = 0 }
}
}
if ($len -ge 4) { $d2Runs++ }
}
Write-Output ("{0}" -f $r.Name)
Write-Output (" 笔画段: 水平={0} 垂直={1} 对角45={2} 对角-45={3} (水平文字期望: 水平占优, 对角极少)" -f $hRuns, $vRuns, $d1Runs, $d2Runs)
Write-Output (" 白色像素分布 上/中/下 = {0}/{1}/{2} (新布局期望: 上段为图标密集, 下段为文字)" -f $topN, $midN, $botN)
}
$bmp.Dispose()
+41
View File
@@ -0,0 +1,41 @@
# 像素级验证轮盘扇形间隙宽度(任意半径处应恒定 ≈ 10px)
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
foreach ($name in 'wheel-light', 'wheel-dark') {
$path = "artifacts\$name.png"
$bmp = [System.Drawing.Bitmap]::FromFile((Resolve-Path $path))
$cx = [int]($bmp.Width / 2); $cy = [int]($bmp.Height / 2)
Write-Output "=== $name ($($bmp.Width)x$($bmp.Height)) ==="
foreach ($radius in 100, 145, 200) {
$steps = 7200
$transparentRuns = New-Object System.Collections.Generic.List[double]
$opaqueRuns = New-Object System.Collections.Generic.List[double]
$runSteps = 0; $prevTransparent = $false
for ($i = 0; $i -le $steps; $i++) {
$ang = ($i / $steps) * 2 * [Math]::PI
$x = [int][Math]::Round($cx + $radius * [Math]::Cos($ang))
$y = [int][Math]::Round($cy + $radius * [Math]::Sin($ang))
$px = $bmp.GetPixel($x, $y)
$transparent = $px.A -lt 30
if ($i -gt 0 -and $transparent -ne $prevTransparent) {
$pxArc = $runSteps / $steps * 2 * [Math]::PI * $radius
if ($prevTransparent) { $transparentRuns.Add($pxArc) } else { $opaqueRuns.Add($pxArc) }
$runSteps = 0
}
$runSteps++; $prevTransparent = $transparent
}
$gaps = @($transparentRuns | Where-Object { $_ -gt 1 -and $_ -lt 40 })
$sectors = @($opaqueRuns | Where-Object { $_ -gt 40 })
$gapAvg = -1; $gapMin = -1; $gapMax = -1
if ($gaps.Count -gt 0) {
$gapAvg = ($gaps | Measure-Object -Average).Average
$gapMin = ($gaps | Measure-Object -Minimum).Minimum
$gapMax = ($gaps | Measure-Object -Maximum).Maximum
}
$line = '半径 {0}: 扇形段数={1}, 间隙段数={2}, 间隙宽度 均值={3:F2}px 最小={4:F2}px 最大={5:F2}px (期望≈10px 且处处相等)' -f $radius, $sectors.Count, $gaps.Count, $gapAvg, $gapMin, $gapMax
Write-Output $line
}
$bmp.Dispose()
}
+17
View File
@@ -0,0 +1,17 @@
# 单实例验证:第二个实例应在 2 秒内自动退出
$ErrorActionPreference = 'Stop'
$exe = 'E:\002_Demo\One_click_run_wpf\src\OneClickRun\bin\Debug\net8.0-windows\OneClickRun.exe'
Get-Process -Name 'OneClickRun' -ErrorAction SilentlyContinue |
Where-Object { $_.Path -eq $exe } |
ForEach-Object { Stop-Process -Id $_.Id -Force }
$first = Start-Process -FilePath $exe -PassThru
Start-Sleep -Seconds 3
$second = Start-Process -FilePath $exe -PassThru
Start-Sleep -Seconds 2
$second.Refresh()
Write-Output "FirstAlive: $(-not $first.HasExited)"
Write-Output "SecondExited: $($second.HasExited)"
if (-not $second.HasExited) { Stop-Process -Id $second.Id -Force }
Stop-Process -Id $first.Id -Force
Write-Output 'Done'
+46
View File
@@ -0,0 +1,46 @@
# 一键运行 - 启动冒烟验证脚本(PowerShell 7.6.5
$ErrorActionPreference = 'Stop'
$exe = 'E:\002_Demo\One_click_run_wpf\src\OneClickRun\bin\Debug\net8.0-windows\OneClickRun.exe'
if (-not (Test-Path $exe)) { throw "未找到构建产物: $exe" }
# 清理上次冒烟残留(仅本路径实例)
Get-Process -Name 'OneClickRun' -ErrorAction SilentlyContinue |
Where-Object { $_.Path -eq $exe } |
ForEach-Object { Stop-Process -Id $_.Id -Force }
$logFile = Join-Path $env:APPDATA 'OneClickRun\logs\app-{0}.log' -f (Get-Date -Format 'yyyyMMdd')
$logBefore = 0
if (Test-Path $logFile) { $logBefore = (Get-Item $logFile).Length }
$p = Start-Process -FilePath $exe -PassThru
Start-Sleep -Seconds 6
$p.Refresh()
$alive = -not $p.HasExited
Write-Output "ProcessAlive: $alive"
$cfg = Join-Path $env:APPDATA 'OneClickRun\config.json'
Write-Output "ConfigExists: $(Test-Path $cfg)"
if (Test-Path $cfg) {
$json = Get-Content $cfg -Raw | ConvertFrom-Json
Write-Output "ConfigItems: $($json.items.Count)"
Write-Output "HotkeyText: $($json.general.hotkey.modifiers -join '+')+$($json.general.hotkey.key)"
Write-Output "TriggerMode: $($json.general.triggerMode)"
Write-Output "SelectionMode: $($json.general.selectionMode)"
Write-Output "Theme: $($json.general.theme)"
}
Write-Output '--- 本次启动新增日志 ---'
if (Test-Path $logFile) {
$stream = [System.IO.File]::Open($logFile, 'Open', 'Read', 'ReadWrite')
$reader = New-Object System.IO.StreamReader($stream)
$reader.BaseStream.Seek($logBefore, 'Begin') | Out-Null
$delta = $reader.ReadToEnd()
$reader.Close(); $stream.Close()
Write-Output $delta
}
if ($alive) {
Stop-Process -Id $p.Id -Force
Write-Output 'ProcessStopped: true'
}
Write-Output 'SmokeDone'