diff --git a/README.md b/README.md index 58e96f1..ca67b83 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # AX Commander +- 업데이트: 2026-04-15 21:19 (KST) +- AX Copilot 앱 아이콘을 더 크게 보이도록 다시 생성했습니다. `src/AxCopilot/Assets/icon.ico`의 실제 도형 점유율이 작아 작업 표시줄과 트레이에서 다른 앱보다 작게 보였는데, 같은 4다이아몬드 계열 형태를 유지한 채 내부 여백을 줄이고 캔버스를 더 넓게 쓰도록 멀티사이즈 아이콘을 다시 만들었습니다. +- 트레이 DPI 대응도 함께 보강했습니다. `tools/IconGenerator/Program.cs`가 현재 앱 아이콘 스타일을 기본으로 생성하고 `16/20/24/32/40/48/64/128/256` 프레임을 포함하도록 바뀌어, `src/AxCopilot/App.xaml.cs`의 `LoadAppIcon()`이 고DPI 트레이 크기에서도 더 알맞은 프레임을 읽게 됩니다. +- `src/AxCopilot/Assets/diamond_pixel.svg`도 같은 레이아웃으로 정리해 소스 자산과 실제 `icon.ico`가 서로 덜 어긋나게 맞췄습니다. +- 검증: + - `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_icon_size\\ -p:IntermediateOutputPath=obj\\verify_icon_size\\` 경고 0 / 오류 0 + - `System.Drawing.Icon` 확인: 16/20/24/32 프레임 모두 요청 크기 그대로 로드 + - 업데이트: 2026-04-15 21:11 (KST) - AX Agent 좌측 대화 목록 선택 카드의 배경이 실제로 보이지 않던 회귀를 바로잡았습니다. `src/AxCopilot/Views/ChatWindow.xaml`에서 `ConversationItemTemplate` 루트 `Border`의 `Background`/`BorderBrush` 로컬값을 제거하고 스타일 기본값으로 내린 뒤, 선택 트리거가 `ItemSelectedBackground`를 정상 적용하도록 고쳤습니다. - 같은 파일에서 `ConversationItemsControl`의 컨테이너를 가로 `Stretch`로 맞춰 선택 배경이 제목 주변만이 아니라 행 전체 둥근 카드로 깔리게 했고, idle 심볼은 `16x16` 그리드와 내부 마진을 줘 점선 링이 살짝 잘리던 문제도 함께 줄였습니다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index a33f8b7..37952ed 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1577,3 +1577,10 @@ UI ?遺우쁽????域뱀뮆???귐뗫솯?醫딆춦 ???袁る퓮 ?臾믩씜 ??疫 - idle 심볼은 `16x16` 영역과 내부 마진을 주도록 수정해 점선 링이 가장자리에서 약간 잘리던 문제를 줄였습니다. - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_conversation_list_selection_fix\\ -p:IntermediateOutputPath=obj\\verify_conversation_list_selection_fix\\` 경고 0 / 오류 0 - 검증: `dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "ConversationItemViewModelTests" -p:OutputPath=bin\\verify_conversation_list_selection_fix_tests\\ -p:IntermediateOutputPath=obj\\verify_conversation_list_selection_fix_tests\\` 통과 3 + +업데이트: 2026-04-15 21:19 (KST) +- AX Copilot 앱 아이콘이 작업 표시줄과 트레이에서 작게 보이던 문제를 자산 기준으로 조정했습니다. 기존 `src/AxCopilot/Assets/icon.ico`는 내부 여백이 커서 32px 기준 실사용 영역이 작았고, 트레이에서도 같은 아이콘이 더 축소돼 보였습니다. +- `tools/IconGenerator/Program.cs`를 현재 AX 4다이아몬드 아이콘 스타일 기준 생성기로 정리하고, `16/20/24/32/40/48/64/128/256` 프레임을 포함하는 멀티사이즈 `ico`를 만들도록 바꿨습니다. 이 변경으로 `src/AxCopilot/App.xaml.cs`의 `LoadAppIcon()`이 DPI별 트레이 크기에서도 더 맞는 프레임을 읽을 수 있습니다. +- `src/AxCopilot/Assets/icon.ico`는 내부 도형 점유율을 키운 새 아이콘으로 재생성했고, `src/AxCopilot/Assets/diamond_pixel.svg`도 같은 비율의 소스 자산으로 맞췄습니다. +- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_icon_size\\ -p:IntermediateOutputPath=obj\\verify_icon_size\\` 경고 0 / 오류 0 +- 검증: `System.Drawing.Icon` 확인 결과 16/20/24/32 프레임이 요청 크기 그대로 로드됨 diff --git a/src/AxCopilot/Assets/diamond_pixel.svg b/src/AxCopilot/Assets/diamond_pixel.svg index 48d7a81..0da1f27 100644 --- a/src/AxCopilot/Assets/diamond_pixel.svg +++ b/src/AxCopilot/Assets/diamond_pixel.svg @@ -1,13 +1,27 @@ - + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + diff --git a/src/AxCopilot/Assets/icon.ico b/src/AxCopilot/Assets/icon.ico index 273db74..5cffa14 100644 Binary files a/src/AxCopilot/Assets/icon.ico and b/src/AxCopilot/Assets/icon.ico differ diff --git a/tools/IconGenerator/Program.cs b/tools/IconGenerator/Program.cs index 3688a0f..36dcd0a 100644 --- a/tools/IconGenerator/Program.cs +++ b/tools/IconGenerator/Program.cs @@ -2,30 +2,115 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; -// 다이아몬드 픽셀 아이콘 생성기 v4 -// 보석 다이아몬드 컷 실루엣 (flat top → wide girdle → bottom point) -// 내부 facet 선 + RGBG 4색 채움 -// 참고: Samsung Diamond Pixel 구조 (흰색선 다이아몬드 도형) +var options = IconGeneratorOptions.Parse(args); +var pngFrames = new List(); -var outputPath = args.Length > 0 ? args[0] - : Path.Combine(AppContext.BaseDirectory, "icon.ico"); - -int[] sizes = [16, 24, 32, 48, 64, 128, 256]; -var pngList = new List(); - -foreach (var sz in sizes) +foreach (var size in options.Sizes) { - using var bmp = DrawGemDiamond(sz); - using var ms = new MemoryStream(); - bmp.Save(ms, ImageFormat.Png); - pngList.Add(ms.ToArray()); - Console.WriteLine($" {sz}x{sz} OK"); + using var bitmap = options.Style switch + { + IconStyle.LegacyGem => DrawLegacyGemDiamond(size), + _ => DrawAxDiamondPixel(size), + }; + + using var stream = new MemoryStream(); + bitmap.Save(stream, ImageFormat.Png); + pngFrames.Add(stream.ToArray()); + Console.WriteLine($" {size}x{size} OK"); + + if (options.PreviewSize == size && !string.IsNullOrWhiteSpace(options.PreviewPath)) + bitmap.Save(options.PreviewPath!, ImageFormat.Png); } -CreateIco(pngList, sizes, outputPath); -Console.WriteLine($"Icon saved: {outputPath}"); +CreateIco(pngFrames, options.Sizes, options.OutputPath); +Console.WriteLine($"Icon saved: {options.OutputPath}"); -static Bitmap DrawGemDiamond(int size) +static Bitmap DrawAxDiamondPixel(int size) +{ + var bitmap = new Bitmap(size, size, PixelFormat.Format32bppArgb); + using var g = Graphics.FromImage(bitmap); + g.Clear(Color.Transparent); + g.SmoothingMode = SmoothingMode.AntiAlias; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + + var canvas = size; + var groupSpan = canvas * 0.55f; + var tileSize = canvas * 0.245f; + var gap = Math.Max(1f, groupSpan - tileSize * 2f); + var left = (canvas - groupSpan) / 2f; + var top = left; + var radius = Math.Max(2f, tileSize * 0.16f); + var center = new PointF(canvas / 2f, canvas / 2f); + + var tiles = new[] + { + new IconTile( + new RectangleF(left, top, tileSize, tileSize), + Color.FromArgb(0x74, 0x8E, 0xFF), + Color.FromArgb(0x5B, 0x70, 0xF7)), + new IconTile( + new RectangleF(left + tileSize + gap, top, tileSize, tileSize), + Color.FromArgb(0x7C, 0xF0, 0x88), + Color.FromArgb(0x42, 0xD6, 0x63)), + new IconTile( + new RectangleF(left, top + tileSize + gap, tileSize, tileSize), + Color.FromArgb(0x64, 0xDB, 0x8B), + Color.FromArgb(0x36, 0xC6, 0x78)), + new IconTile( + new RectangleF(left + tileSize + gap, top + tileSize + gap, tileSize, tileSize), + Color.FromArgb(0xFF, 0x6C, 0x84), + Color.FromArgb(0xF2, 0x47, 0x69)), + }; + + var shadowOffset = Math.Max(0.45f, canvas * 0.02f); + using var shadowBrush = new SolidBrush(Color.FromArgb((int)(255 * 0.16f), 0x2A, 0x34, 0x56)); + foreach (var tile in tiles) + { + using var shadowPath = CreateRoundedRectangle(new RectangleF( + tile.Bounds.X + shadowOffset, + tile.Bounds.Y + shadowOffset, + tile.Bounds.Width, + tile.Bounds.Height), radius); + using var matrix = new Matrix(); + matrix.RotateAt(45f, center); + shadowPath.Transform(matrix); + g.FillPath(shadowBrush, shadowPath); + } + + foreach (var tile in tiles) + { + using var path = CreateRoundedRectangle(tile.Bounds, radius); + using var matrix = new Matrix(); + matrix.RotateAt(45f, center); + path.Transform(matrix); + + using var brush = new LinearGradientBrush(tile.Bounds, tile.Highlight, tile.Base, 45f, true); + g.FillPath(brush, path); + + using var outline = new Pen(Color.FromArgb((int)(255 * 0.26f), 255, 255, 255), Math.Max(0.7f, canvas / 60f)) + { + LineJoin = LineJoin.Round + }; + g.DrawPath(outline, path); + } + + using var shineBrush = new SolidBrush(Color.FromArgb((int)(255 * 0.18f), 255, 255, 255)); + var shineSize = Math.Max(1.2f, canvas * 0.07f); + foreach (var point in new[] + { + new PointF(canvas * 0.30f, canvas * 0.28f), + new PointF(canvas * 0.67f, canvas * 0.30f), + new PointF(canvas * 0.34f, canvas * 0.66f), + }) + { + g.FillEllipse(shineBrush, point.X, point.Y, shineSize, shineSize); + } + + return bitmap; +} + +static Bitmap DrawLegacyGemDiamond(int size) { var bmp = new Bitmap(size, size, PixelFormat.Format32bppArgb); using var g = Graphics.FromImage(bmp); @@ -33,173 +118,211 @@ static Bitmap DrawGemDiamond(int size) g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.Clear(Color.Transparent); - float s = size; - float cx = s / 2f; + var s = size; + var cx = s / 2f; + var tableL = s * 0.22f; + var tableR = s * 0.78f; + var tableY = s * 0.18f; + var girdleL = s * 0.06f; + var girdleR = s * 0.94f; + var girdleY = s * 0.40f; + var culetX = cx; + var culetY = s * 0.92f; - // ── 보석 다이아몬드 외곽 좌표 ── - // Table (평평한 윗면) - float tableL = s * 0.22f; - float tableR = s * 0.78f; - float tableY = s * 0.18f; + PointF tl = new(tableL, tableY); + PointF tr = new(tableR, tableY); + PointF gl = new(girdleL, girdleY); + PointF gr = new(girdleR, girdleY); + PointF bt = new(culetX, culetY); + PointF cm = new(cx, girdleY); + PointF ct = new(cx, tableY); - // Girdle (가장 넓은 부분) - float girdleL = s * 0.06f; - float girdleR = s * 0.94f; - float girdleY = s * 0.40f; - - // Culet (바닥 뾰족한 점) - float culetX = cx; - float culetY = s * 0.92f; - - // 주요 꼭짓점 - PointF TL = new(tableL, tableY); // 테이블 좌 - PointF TR = new(tableR, tableY); // 테이블 우 - PointF GL = new(girdleL, girdleY); // 거들 좌 - PointF GR = new(girdleR, girdleY); // 거들 우 - PointF BT = new(culetX, culetY); // 바닥 점 - - // Crown 내부 포인트 (table → girdle 사이 facet) - PointF CM = new(cx, girdleY); // 거들 중앙 - PointF CT = new(cx, tableY); // 테이블 중앙 - - // Crown facet 분할점 - PointF CL = new(s * 0.14f, girdleY); // 거들 좌측 근처 - PointF CR = new(s * 0.86f, girdleY); // 거들 우측 근처 - - // Pavilion facet 내부 교차점들 - float pavMidY = s * 0.62f; - PointF PL = new(s * 0.28f, pavMidY); // 파빌리온 좌 중간 - PointF PR = new(s * 0.72f, pavMidY); // 파빌리온 우 중간 - PointF PM = new(cx, pavMidY); // 파빌리온 중앙 - - // ── 색상 ── - var blue = Color.FromArgb(50, 110, 230); + var blue = Color.FromArgb(50, 110, 230); var blueBright = Color.FromArgb(80, 150, 255); - var green = Color.FromArgb(60, 200, 80); + var green = Color.FromArgb(60, 200, 80); var greenBright = Color.FromArgb(100, 235, 110); - var red = Color.FromArgb(230, 50, 65); + var red = Color.FromArgb(230, 50, 65); var redBright = Color.FromArgb(255, 90, 100); - var greenDk = Color.FromArgb(45, 170, 75); + var greenDk = Color.FromArgb(45, 170, 75); var greenDkBr = Color.FromArgb(80, 210, 100); - // ── Crown (상단부) 채우기 ── + FillPolygon(g, new[] { tl, gl, cm, ct }, blueBright, blue); + FillPolygon(g, new[] { tr, ct, cm, gr }, greenBright, green); + FillPolygon(g, new[] { gl, cm, bt }, redBright, red); + FillPolygon(g, new[] { cm, gr, bt }, greenDkBr, greenDk); - // Crown 좌: Blue - FillPoly(g, new[]{TL, GL, CM, CT}, blueBright, blue); - // Crown 우: Green - FillPoly(g, new[]{TR, CT, CM, GR}, greenBright, green); - - // ── Pavilion (하단부) 채우기 ── - - // Pavilion 좌: Red - FillPoly(g, new[]{GL, CM, BT}, redBright, red); - // Pavilion 우: Green (darker) - FillPoly(g, new[]{CM, GR, BT}, greenDkBr, greenDk); - - // ── Facet 내부 색상 변화 (깊이감) ── - // Crown 좌측 어두운 삼각형 using var crownShadow = new SolidBrush(Color.FromArgb(25, 0, 0, 0)); - g.FillPolygon(crownShadow, new[]{TL, GL, new PointF(cx * 0.7f, girdleY * 0.85f)}); + g.FillPolygon(crownShadow, new[] { tl, gl, new PointF(cx * 0.7f, girdleY * 0.85f) }); - // Pavilion 중앙 밝은 삼각형 (반사) using var pavHighlight = new SolidBrush(Color.FromArgb(20, 255, 255, 255)); - g.FillPolygon(pavHighlight, new[]{CM, BT, new PointF(cx - s*0.08f, pavMidY)}); + g.FillPolygon(pavHighlight, new[] { cm, bt, new PointF(cx - s * 0.08f, s * 0.62f) }); - // ── Facet 선 (흰색) ── - float lw = Math.Max(0.8f, s / 140f); - using var facetPen = new Pen(Color.FromArgb(180, 255, 255, 255), lw) + using var facetPen = new Pen(Color.FromArgb(180, 255, 255, 255), Math.Max(0.8f, s / 140f)) { LineJoin = LineJoin.Round, StartCap = LineCap.Round, EndCap = LineCap.Round }; - // Crown facet lines - // 테이블 윗변 - g.DrawLine(facetPen, TL, TR); - // 테이블 → 거들 대각선 - g.DrawLine(facetPen, TL, GL); - g.DrawLine(facetPen, TR, GR); - // 세로 중심선 (table → girdle) - g.DrawLine(facetPen, CT, CM); - // Crown 크로스 facet - g.DrawLine(facetPen, TL, CM); - g.DrawLine(facetPen, TR, CM); - // 추가 Crown facet (table 모서리 → 거들 중간) - g.DrawLine(facetPen, TL, new PointF(girdleL + (cx - girdleL) * 0.5f, girdleY)); - g.DrawLine(facetPen, TR, new PointF(cx + (girdleR - cx) * 0.5f, girdleY)); + g.DrawLine(facetPen, tl, tr); + g.DrawLine(facetPen, tl, gl); + g.DrawLine(facetPen, tr, gr); + g.DrawLine(facetPen, ct, cm); + g.DrawLine(facetPen, tl, cm); + g.DrawLine(facetPen, tr, cm); + g.DrawLine(facetPen, tl, new PointF(girdleL + (cx - girdleL) * 0.5f, girdleY)); + g.DrawLine(facetPen, tr, new PointF(cx + (girdleR - cx) * 0.5f, girdleY)); + g.DrawLine(facetPen, gl, gr); + g.DrawLine(facetPen, gl, bt); + g.DrawLine(facetPen, gr, bt); + g.DrawLine(facetPen, cm, bt); - // Girdle 수평선 - g.DrawLine(facetPen, GL, GR); - - // Pavilion facet lines - // 거들 → 바닥 점 - g.DrawLine(facetPen, GL, BT); - g.DrawLine(facetPen, GR, BT); - // 중심 → 바닥 - g.DrawLine(facetPen, CM, BT); - // Pavilion 크로스 facets - float crossY = girdleY + (culetY - girdleY) * 0.45f; + var crossY = girdleY + (culetY - girdleY) * 0.45f; PointF crossL = new(girdleL + (culetX - girdleL) * 0.45f, crossY); PointF crossR = new(girdleR - (girdleR - culetX) * 0.45f, crossY); - g.DrawLine(facetPen, GL, crossR); - g.DrawLine(facetPen, GR, crossL); - // 거들 중간점에서 바닥으로 - g.DrawLine(facetPen, new PointF(girdleL + (cx - girdleL) * 0.5f, girdleY), BT); - g.DrawLine(facetPen, new PointF(cx + (girdleR - cx) * 0.5f, girdleY), BT); + g.DrawLine(facetPen, gl, crossR); + g.DrawLine(facetPen, gr, crossL); + g.DrawLine(facetPen, new PointF(girdleL + (cx - girdleL) * 0.5f, girdleY), bt); + g.DrawLine(facetPen, new PointF(cx + (girdleR - cx) * 0.5f, girdleY), bt); - // ── 외곽선 (두꺼운 흰색) ── - float olw = Math.Max(1.2f, s / 100f); - using var outlinePen = new Pen(Color.FromArgb(220, 255, 255, 255), olw) + using var outlinePen = new Pen(Color.FromArgb(220, 255, 255, 255), Math.Max(1.2f, s / 100f)) { LineJoin = LineJoin.Round }; - g.DrawPolygon(outlinePen, new[]{TL, TR, GR, BT, GL}); + g.DrawPolygon(outlinePen, new[] { tl, tr, gr, bt, gl }); - // ── 상단 하이라이트 (테이블 면 빛 반사) ── - using var tableHL = new LinearGradientBrush( - TL, new PointF(cx, girdleY), + using var tableHighlight = new LinearGradientBrush( + tl, + new PointF(cx, girdleY), Color.FromArgb(45, 255, 255, 255), Color.Transparent); - g.FillPolygon(tableHL, new[]{TL, TR, CT}); + g.FillPolygon(tableHighlight, new[] { tl, tr, ct }); return bmp; } -static void FillPoly(Graphics g, PointF[] pts, Color c1, Color c2) +static GraphicsPath CreateRoundedRectangle(RectangleF bounds, float radius) { - float minX = pts.Min(p => p.X), maxX = pts.Max(p => p.X); - float minY = pts.Min(p => p.Y), maxY = pts.Max(p => p.Y); - var rect = new RectangleF(minX, minY, Math.Max(1, maxX - minX), Math.Max(1, maxY - minY)); - try + var path = new GraphicsPath(); + var diameter = radius * 2f; + + if (radius <= 0.01f) { - using var brush = new LinearGradientBrush(rect, c1, c2, LinearGradientMode.Vertical); - g.FillPolygon(brush, pts); - } - catch - { - using var brush = new SolidBrush(c1); - g.FillPolygon(brush, pts); + path.AddRectangle(bounds); + return path; } + + path.AddArc(bounds.Left, bounds.Top, diameter, diameter, 180, 90); + path.AddArc(bounds.Right - diameter, bounds.Top, diameter, diameter, 270, 90); + path.AddArc(bounds.Right - diameter, bounds.Bottom - diameter, diameter, diameter, 0, 90); + path.AddArc(bounds.Left, bounds.Bottom - diameter, diameter, diameter, 90, 90); + path.CloseFigure(); + return path; } -static void CreateIco(List pngs, int[] sizes, string path) +static void FillPolygon(Graphics g, PointF[] points, Color topColor, Color bottomColor) { - using var ms = new MemoryStream(); - using var bw = new BinaryWriter(ms); - bw.Write((short)0); - bw.Write((short)1); - bw.Write((short)pngs.Count); - int offset = 6 + 16 * pngs.Count; - for (int i = 0; i < pngs.Count; i++) + var minX = points.Min(p => p.X); + var maxX = points.Max(p => p.X); + var minY = points.Min(p => p.Y); + var maxY = points.Max(p => p.Y); + var rect = new RectangleF(minX, minY, Math.Max(1, maxX - minX), Math.Max(1, maxY - minY)); + + using var brush = new LinearGradientBrush(rect, topColor, bottomColor, LinearGradientMode.Vertical); + g.FillPolygon(brush, points); +} + +static void CreateIco(IReadOnlyList pngs, IReadOnlyList sizes, string outputPath) +{ + using var memoryStream = new MemoryStream(); + using var writer = new BinaryWriter(memoryStream); + + writer.Write((short)0); + writer.Write((short)1); + writer.Write((short)pngs.Count); + + var offset = 6 + 16 * pngs.Count; + for (var i = 0; i < pngs.Count; i++) { - byte dim = (byte)(sizes[i] >= 256 ? 0 : sizes[i]); - bw.Write(dim); bw.Write(dim); - bw.Write((byte)0); bw.Write((byte)0); - bw.Write((short)1); bw.Write((short)32); - bw.Write(pngs[i].Length); bw.Write(offset); + var dimension = (byte)(sizes[i] >= 256 ? 0 : sizes[i]); + writer.Write(dimension); + writer.Write(dimension); + writer.Write((byte)0); + writer.Write((byte)0); + writer.Write((short)1); + writer.Write((short)32); + writer.Write(pngs[i].Length); + writer.Write(offset); offset += pngs[i].Length; } - foreach (var png in pngs) bw.Write(png); - File.WriteAllBytes(path, ms.ToArray()); + + foreach (var png in pngs) + writer.Write(png); + + File.WriteAllBytes(outputPath, memoryStream.ToArray()); +} + +file sealed record IconTile(RectangleF Bounds, Color Highlight, Color Base); + +file enum IconStyle +{ + AxDiamond, + LegacyGem, +} + +file sealed class IconGeneratorOptions +{ + public string OutputPath { get; init; } = ""; + public string? PreviewPath { get; init; } + public int PreviewSize { get; init; } = 256; + public int[] Sizes { get; init; } = [16, 20, 24, 32, 40, 48, 64, 128, 256]; + public IconStyle Style { get; init; } = IconStyle.AxDiamond; + + public static IconGeneratorOptions Parse(string[] args) + { + string? outputPath = null; + string? previewPath = null; + var previewSize = 256; + var style = IconStyle.AxDiamond; + + foreach (var arg in args) + { + if (arg.StartsWith("--preview=", StringComparison.OrdinalIgnoreCase)) + { + previewPath = arg["--preview=".Length..].Trim('"'); + continue; + } + + if (arg.StartsWith("--preview-size=", StringComparison.OrdinalIgnoreCase) + && int.TryParse(arg["--preview-size=".Length..], out var parsedPreviewSize)) + { + previewSize = parsedPreviewSize; + continue; + } + + if (arg.StartsWith("--style=", StringComparison.OrdinalIgnoreCase)) + { + var styleValue = arg["--style=".Length..].Trim().ToLowerInvariant(); + style = styleValue switch + { + "legacy-gem" or "gem" => IconStyle.LegacyGem, + _ => IconStyle.AxDiamond, + }; + continue; + } + + if (!arg.StartsWith("--", StringComparison.Ordinal)) + outputPath ??= arg.Trim('"'); + } + + outputPath ??= Path.Combine(AppContext.BaseDirectory, "icon.ico"); + + return new IconGeneratorOptions + { + OutputPath = outputPath, + PreviewPath = previewPath, + PreviewSize = previewSize, + Style = style, + }; + } }