use common

This commit is contained in:
Leilei Zhang
2025-12-05 13:12:43 +08:00
parent d987d37fff
commit b4cdec5e5d
4 changed files with 636 additions and 735 deletions

View File

@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace FancyZonesCLI;
/// <summary>
/// Provides methods to read and write FancyZones configuration data.
/// </summary>
internal static class FancyZonesData
{
/// <summary>
/// Try to read applied layouts configuration.
/// </summary>
public static bool TryReadAppliedLayouts(out AppliedLayouts result, out string error)
{
return TryReadJsonFile(FancyZonesPaths.AppliedLayouts, FancyZonesJsonContext.Default.AppliedLayouts, out result, out error);
}
/// <summary>
/// Read applied layouts or return null if not found.
/// </summary>
public static AppliedLayouts ReadAppliedLayouts()
{
return ReadJsonFileOrDefault(FancyZonesPaths.AppliedLayouts, FancyZonesJsonContext.Default.AppliedLayouts);
}
/// <summary>
/// Write applied layouts configuration.
/// </summary>
public static void WriteAppliedLayouts(AppliedLayouts layouts)
{
WriteJsonFile(FancyZonesPaths.AppliedLayouts, layouts, FancyZonesJsonContext.Default.AppliedLayouts);
}
/// <summary>
/// Read custom layouts or return null if not found.
/// </summary>
public static CustomLayouts ReadCustomLayouts()
{
return ReadJsonFileOrDefault(FancyZonesPaths.CustomLayouts, FancyZonesJsonContext.Default.CustomLayouts);
}
/// <summary>
/// Read layout templates or return null if not found.
/// </summary>
public static LayoutTemplates ReadLayoutTemplates()
{
return ReadJsonFileOrDefault(FancyZonesPaths.LayoutTemplates, FancyZonesJsonContext.Default.LayoutTemplates);
}
/// <summary>
/// Read layout hotkeys or return null if not found.
/// </summary>
public static LayoutHotkeys ReadLayoutHotkeys()
{
return ReadJsonFileOrDefault(FancyZonesPaths.LayoutHotkeys, FancyZonesJsonContext.Default.LayoutHotkeys);
}
/// <summary>
/// Write layout hotkeys configuration.
/// </summary>
public static void WriteLayoutHotkeys(LayoutHotkeys hotkeys)
{
WriteJsonFile(FancyZonesPaths.LayoutHotkeys, hotkeys, FancyZonesJsonContext.Default.LayoutHotkeys);
}
/// <summary>
/// Check if editor parameters file exists.
/// </summary>
public static bool EditorParametersExist()
{
return File.Exists(FancyZonesPaths.EditorParameters);
}
private static bool TryReadJsonFile<T>(string filePath, JsonTypeInfo<T> jsonTypeInfo, out T result, out string error)
where T : class
{
result = null;
error = null;
if (!File.Exists(filePath))
{
error = $"File not found: {Path.GetFileName(filePath)}";
return false;
}
try
{
var json = File.ReadAllText(filePath);
result = JsonSerializer.Deserialize(json, jsonTypeInfo);
if (result == null)
{
error = $"Failed to parse {Path.GetFileName(filePath)}";
return false;
}
return true;
}
catch (JsonException ex)
{
error = $"JSON parse error in {Path.GetFileName(filePath)}: {ex.Message}";
return false;
}
catch (IOException ex)
{
error = $"Failed to read {Path.GetFileName(filePath)}: {ex.Message}";
return false;
}
}
private static T ReadJsonFileOrDefault<T>(string filePath, JsonTypeInfo<T> jsonTypeInfo, T defaultValue = null)
where T : class
{
if (TryReadJsonFile(filePath, jsonTypeInfo, out var result, out _))
{
return result;
}
return defaultValue;
}
private static void WriteJsonFile<T>(string filePath, T data, JsonTypeInfo<T> jsonTypeInfo)
{
var json = JsonSerializer.Serialize(data, jsonTypeInfo);
File.WriteAllText(filePath, json);
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
namespace FancyZonesCLI;
/// <summary>
/// Provides paths to FancyZones configuration files.
/// </summary>
internal static class FancyZonesPaths
{
private static readonly string DataPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Microsoft",
"PowerToys",
"FancyZones");
public static string AppliedLayouts => Path.Combine(DataPath, "applied-layouts.json");
public static string CustomLayouts => Path.Combine(DataPath, "custom-layouts.json");
public static string LayoutTemplates => Path.Combine(DataPath, "layout-templates.json");
public static string LayoutHotkeys => Path.Combine(DataPath, "layout-hotkeys.json");
public static string EditorParameters => Path.Combine(DataPath, "editor-parameters.json");
}

View File

@@ -4,7 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Globalization;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
@@ -12,20 +12,21 @@ namespace FancyZonesCLI;
public static class LayoutVisualizer public static class LayoutVisualizer
{ {
public static void DrawTemplateLayout(TemplateLayout template) public static string DrawTemplateLayout(TemplateLayout template)
{ {
Console.WriteLine(" Visual Preview:"); var sb = new StringBuilder();
sb.AppendLine(" Visual Preview:");
switch (template.Type.ToLowerInvariant()) switch (template.Type.ToLowerInvariant())
{ {
case "focus": case "focus":
DrawFocusLayout(template.ZoneCount > 0 ? template.ZoneCount : 3); sb.Append(RenderFocusLayout(template.ZoneCount > 0 ? template.ZoneCount : 3));
break; break;
case "columns": case "columns":
DrawGridLayout(1, template.ZoneCount > 0 ? template.ZoneCount : 3); sb.Append(RenderGridLayout(1, template.ZoneCount > 0 ? template.ZoneCount : 3));
break; break;
case "rows": case "rows":
DrawGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3, 1); sb.Append(RenderGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3, 1));
break; break;
case "grid": case "grid":
// Grid layout: calculate rows and columns from zone count // Grid layout: calculate rows and columns from zone count
@@ -45,28 +46,31 @@ public static class LayoutVisualizer
cols++; cols++;
} }
DrawGridLayoutWithZoneCount(rows, cols, zoneCount); sb.Append(RenderGridLayoutWithZoneCount(rows, cols, zoneCount));
break; break;
case "priority-grid": case "priority-grid":
DrawPriorityGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3); sb.Append(RenderPriorityGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3));
break; break;
case "blank": case "blank":
Console.WriteLine(" (No zones)"); sb.AppendLine(" (No zones)");
break; break;
default: default:
Console.WriteLine($" ({template.Type} layout)"); sb.AppendLine(CultureInfo.InvariantCulture, $" ({template.Type} layout)");
break; break;
} }
return sb.ToString();
} }
public static void DrawCustomLayout(CustomLayout layout) public static string DrawCustomLayout(CustomLayout layout)
{ {
if (layout.Info.ValueKind == JsonValueKind.Undefined || layout.Info.ValueKind == JsonValueKind.Null) if (layout.Info.ValueKind == JsonValueKind.Undefined || layout.Info.ValueKind == JsonValueKind.Null)
{ {
return; return string.Empty;
} }
Console.WriteLine(" Visual Preview:"); var sb = new StringBuilder();
sb.AppendLine(" Visual Preview:");
if (layout.Type == "grid" && if (layout.Type == "grid" &&
layout.Info.TryGetProperty("rows", out var rows) && layout.Info.TryGetProperty("rows", out var rows) &&
@@ -78,12 +82,12 @@ public static class LayoutVisualizer
// Check if there's a cell-child-map (merged cells) // Check if there's a cell-child-map (merged cells)
if (layout.Info.TryGetProperty("cell-child-map", out var cellMap)) if (layout.Info.TryGetProperty("cell-child-map", out var cellMap))
{ {
DrawGridLayoutWithMergedCells(r, c, cellMap); sb.Append(RenderGridLayoutWithMergedCells(r, c, cellMap));
} }
else else
{ {
int height = r >= 4 ? 12 : 8; int height = r >= 4 ? 12 : 8;
DrawGridLayout(r, c, 30, height); sb.Append(RenderGridLayout(r, c, 30, height));
} }
} }
else if (layout.Type == "canvas" && else if (layout.Type == "canvas" &&
@@ -91,60 +95,61 @@ public static class LayoutVisualizer
layout.Info.TryGetProperty("ref-width", out var refWidth) && layout.Info.TryGetProperty("ref-width", out var refWidth) &&
layout.Info.TryGetProperty("ref-height", out var refHeight)) layout.Info.TryGetProperty("ref-height", out var refHeight))
{ {
DrawCanvasLayout(zones, refWidth.GetInt32(), refHeight.GetInt32()); sb.Append(RenderCanvasLayout(zones, refWidth.GetInt32(), refHeight.GetInt32()));
} }
return sb.ToString();
} }
private static void DrawFocusLayout(int zoneCount = 3) private static string RenderFocusLayout(int zoneCount = 3)
{ {
var sb = new StringBuilder();
// Focus layout: overlapping zones with cascading offset // Focus layout: overlapping zones with cascading offset
// Show first 2, ellipsis, and last 1 if more than 4 zones
if (zoneCount == 1) if (zoneCount == 1)
{ {
Console.WriteLine(" +-------+"); sb.AppendLine(" +-------+");
Console.WriteLine(" | |"); sb.AppendLine(" | |");
Console.WriteLine(" | |"); sb.AppendLine(" | |");
Console.WriteLine(" +-------+"); sb.AppendLine(" +-------+");
} }
else if (zoneCount == 2) else if (zoneCount == 2)
{ {
Console.WriteLine(" +-------+"); sb.AppendLine(" +-------+");
Console.WriteLine(" | |"); sb.AppendLine(" | |");
Console.WriteLine(" | +-------+"); sb.AppendLine(" | +-------+");
Console.WriteLine(" +-| |"); sb.AppendLine(" +-| |");
Console.WriteLine(" | |"); sb.AppendLine(" | |");
Console.WriteLine(" +-------+"); sb.AppendLine(" +-------+");
} }
else else
{ {
Console.WriteLine(" +-------+"); sb.AppendLine(" +-------+");
Console.WriteLine(" | |"); sb.AppendLine(" | |");
Console.WriteLine(" | +-------+"); sb.AppendLine(" | +-------+");
Console.WriteLine(" +-| |"); sb.AppendLine(" +-| |");
Console.WriteLine(" | +-------+"); sb.AppendLine(" | +-------+");
Console.WriteLine(" +-| |"); sb.AppendLine(" +-| |");
sb.AppendLine(" ...");
// Middle ellipsis sb.AppendLine(CultureInfo.InvariantCulture, $" (total: {zoneCount} zones)");
Console.WriteLine(" ..."); sb.AppendLine(" ...");
Console.WriteLine($" (total: {zoneCount} zones)"); sb.AppendLine(" | +-------+");
Console.WriteLine(" ..."); sb.AppendLine(" +-| |");
sb.AppendLine(" | |");
// Show indication of last zone (without full indent) sb.AppendLine(" +-------+");
Console.WriteLine(" | +-------+");
Console.WriteLine(" +-| |");
Console.WriteLine(" | |");
Console.WriteLine(" +-------+");
} }
return sb.ToString();
} }
private static void DrawPriorityGridLayout(int zoneCount = 3) private static string RenderPriorityGridLayout(int zoneCount = 3)
{ {
// Priority Grid has predefined layouts for zone counts 1-11 // Priority Grid has predefined layouts for zone counts 1-11
// Data format from GridLayoutModel._priorityData // Data format from GridLayoutModel._priorityData
if (zoneCount >= 1 && zoneCount <= 11) if (zoneCount >= 1 && zoneCount <= 11)
{ {
int[,] cellMap = GetPriorityGridCellMap(zoneCount); int[,] cellMap = GetPriorityGridCellMap(zoneCount);
DrawGridLayoutWithCellMap(cellMap); return RenderGridLayoutWithCellMap(cellMap);
} }
else else
{ {
@@ -162,7 +167,7 @@ public static class LayoutVisualizer
cols++; cols++;
} }
DrawGridLayoutWithZoneCount(rows, cols, zoneCount); return RenderGridLayoutWithZoneCount(rows, cols, zoneCount);
} }
} }
@@ -186,8 +191,9 @@ public static class LayoutVisualizer
}; };
} }
private static void DrawGridLayoutWithCellMap(int[,] cellMap, int width = 30, int height = 8) private static string RenderGridLayoutWithCellMap(int[,] cellMap, int width = 30, int height = 8)
{ {
var sb = new StringBuilder();
int rows = cellMap.GetLength(0); int rows = cellMap.GetLength(0);
int cols = cellMap.GetLength(1); int cols = cellMap.GetLength(1);
@@ -197,79 +203,54 @@ public static class LayoutVisualizer
for (int r = 0; r < rows; r++) for (int r = 0; r < rows; r++)
{ {
// Top border // Top border
Console.Write(" +"); sb.Append(" +");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
// Check if this cell should merge with the cell above
bool mergeTop = r > 0 && cellMap[r, c] == cellMap[r - 1, c]; bool mergeTop = r > 0 && cellMap[r, c] == cellMap[r - 1, c];
// Check if this cell should merge with the cell to the left
bool mergeLeft = c > 0 && cellMap[r, c] == cellMap[r, c - 1]; bool mergeLeft = c > 0 && cellMap[r, c] == cellMap[r, c - 1];
if (mergeTop) if (mergeTop)
{ {
if (mergeLeft) sb.Append(mergeLeft ? new string(' ', cellWidth) : new string(' ', cellWidth - 1) + "+");
{
Console.Write(new string(' ', cellWidth));
}
else
{
Console.Write(new string(' ', cellWidth - 1));
Console.Write("+");
}
} }
else else
{ {
if (mergeLeft) sb.Append(mergeLeft ? new string('-', cellWidth) : new string('-', cellWidth - 1) + "+");
{
Console.Write(new string('-', cellWidth));
}
else
{
Console.Write(new string('-', cellWidth - 1));
Console.Write("+");
}
} }
} }
Console.WriteLine(); sb.AppendLine();
// Cell content // Cell content
for (int h = 0; h < cellHeight - 1; h++) for (int h = 0; h < cellHeight - 1; h++)
{ {
Console.Write(" "); sb.Append(" ");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
bool mergeLeft = c > 0 && cellMap[r, c] == cellMap[r, c - 1]; bool mergeLeft = c > 0 && cellMap[r, c] == cellMap[r, c - 1];
if (mergeLeft) sb.Append(mergeLeft ? ' ' : '|');
{ sb.Append(' ', cellWidth - 1);
Console.Write(" ");
}
else
{
Console.Write("|");
}
Console.Write(new string(' ', cellWidth - 1));
} }
Console.WriteLine("|"); sb.AppendLine("|");
} }
} }
// Bottom border // Bottom border
Console.Write(" +"); sb.Append(" +");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
Console.Write(new string('-', cellWidth - 1)); sb.Append('-', cellWidth - 1);
Console.Write("+"); sb.Append('+');
} }
Console.WriteLine(); sb.AppendLine();
return sb.ToString();
} }
private static void DrawGridLayoutWithMergedCells(int rows, int cols, JsonElement cellMap) private static string RenderGridLayoutWithMergedCells(int rows, int cols, JsonElement cellMap)
{ {
var sb = new StringBuilder();
const int displayWidth = 39; const int displayWidth = 39;
const int displayHeight = 12; const int displayHeight = 12;
@@ -284,45 +265,20 @@ public static class LayoutVisualizer
} }
} }
// Find unique zones and their count
var zones = new HashSet<int>();
for (int r = 0; r < rows; r++)
{
for (int c = 0; c < cols; c++)
{
zones.Add(zoneMap[r, c]);
}
}
int cellHeight = displayHeight / rows; int cellHeight = displayHeight / rows;
int cellWidth = displayWidth / cols; int cellWidth = displayWidth / cols;
// Draw top border // Draw top border
Console.Write(" +"); sb.Append(" +");
Console.Write(new string('-', displayWidth)); sb.Append('-', displayWidth);
Console.WriteLine("+"); sb.AppendLine("+");
// Draw rows // Draw rows
for (int r = 0; r < rows; r++) for (int r = 0; r < rows; r++)
{ {
// For each row, find the column range of each zone
var zoneRanges = new Dictionary<int, (int Start, int End)>();
for (int c = 0; c < cols; c++)
{
int zone = zoneMap[r, c];
if (zoneRanges.TryGetValue(zone, out var range))
{
zoneRanges[zone] = (range.Start, c);
}
else
{
zoneRanges[zone] = (c, c);
}
}
for (int h = 0; h < cellHeight; h++) for (int h = 0; h < cellHeight; h++)
{ {
Console.Write(" |"); sb.Append(" |");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
@@ -330,47 +286,35 @@ public static class LayoutVisualizer
int leftZone = c > 0 ? zoneMap[r, c - 1] : -1; int leftZone = c > 0 ? zoneMap[r, c - 1] : -1;
bool needLeftBorder = c > 0 && currentZone != leftZone; bool needLeftBorder = c > 0 && currentZone != leftZone;
// Check if this zone has a top border bool zoneHasTopBorder = r > 0 && h == 0 && currentZone != zoneMap[r - 1, c];
bool zoneHasTopBorder = false;
if (r > 0 && h == 0)
{
int topZone = zoneMap[r - 1, c];
zoneHasTopBorder = currentZone != topZone;
}
// Draw left border if needed
if (needLeftBorder) if (needLeftBorder)
{ {
Console.Write("|"); sb.Append('|');
sb.Append(zoneHasTopBorder ? '-' : ' ', cellWidth - 1);
// Fill rest of cell
for (int w = 1; w < cellWidth; w++)
{
Console.Write(zoneHasTopBorder ? "-" : " ");
}
} }
else else
{ {
// No left border, fill entire cell sb.Append(zoneHasTopBorder ? '-' : ' ', cellWidth);
for (int w = 0; w < cellWidth; w++)
{
Console.Write(zoneHasTopBorder ? "-" : " ");
}
} }
} }
Console.WriteLine("|"); sb.AppendLine("|");
} }
} }
// Draw bottom border // Draw bottom border
Console.Write(" +"); sb.Append(" +");
Console.Write(new string('-', displayWidth)); sb.Append('-', displayWidth);
Console.WriteLine("+"); sb.AppendLine("+");
return sb.ToString();
} }
public static void DrawGridLayoutWithZoneCount(int rows, int cols, int zoneCount, int width = 30, int height = 8) public static string RenderGridLayoutWithZoneCount(int rows, int cols, int zoneCount, int width = 30, int height = 8)
{ {
var sb = new StringBuilder();
// Build zone map like Editor's InitGrid // Build zone map like Editor's InitGrid
int[,] zoneMap = new int[rows, cols]; int[,] zoneMap = new int[rows, cols];
int index = 0; int index = 0;
@@ -392,103 +336,93 @@ public static class LayoutVisualizer
for (int r = 0; r < rows; r++) for (int r = 0; r < rows; r++)
{ {
// Top border // Top border
Console.Write(" +"); sb.Append(" +");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
// Check if this cell should merge with the previous one (same zone)
bool mergeLeft = c > 0 && zoneMap[r, c] == zoneMap[r, c - 1]; bool mergeLeft = c > 0 && zoneMap[r, c] == zoneMap[r, c - 1];
if (mergeLeft) sb.Append('-', mergeLeft ? cellWidth : cellWidth - 1);
if (!mergeLeft)
{ {
Console.Write(new string('-', cellWidth)); sb.Append('+');
}
else
{
Console.Write(new string('-', cellWidth - 1));
Console.Write("+");
} }
} }
Console.WriteLine(); sb.AppendLine();
// Cell content // Cell content
for (int h = 0; h < cellHeight - 1; h++) for (int h = 0; h < cellHeight - 1; h++)
{ {
Console.Write(" "); sb.Append(" ");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
// Check if this cell should merge with the previous one
bool mergeLeft = c > 0 && zoneMap[r, c] == zoneMap[r, c - 1]; bool mergeLeft = c > 0 && zoneMap[r, c] == zoneMap[r, c - 1];
if (mergeLeft) sb.Append(mergeLeft ? ' ' : '|');
{ sb.Append(' ', cellWidth - 1);
Console.Write(" ");
}
else
{
Console.Write("|");
}
Console.Write(new string(' ', cellWidth - 1));
} }
Console.WriteLine("|"); sb.AppendLine("|");
} }
} }
// Bottom border // Bottom border
Console.Write(" +"); sb.Append(" +");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
Console.Write(new string('-', cellWidth - 1)); sb.Append('-', cellWidth - 1);
Console.Write("+"); sb.Append('+');
} }
Console.WriteLine(); sb.AppendLine();
return sb.ToString();
} }
public static void DrawGridLayout(int rows, int cols, int width = 30, int height = 8) public static string RenderGridLayout(int rows, int cols, int width = 30, int height = 8)
{ {
var sb = new StringBuilder();
int cellWidth = width / cols; int cellWidth = width / cols;
int cellHeight = height / rows; int cellHeight = height / rows;
for (int r = 0; r < rows; r++) for (int r = 0; r < rows; r++)
{ {
// Top border // Top border
Console.Write(" +"); sb.Append(" +");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
Console.Write(new string('-', cellWidth - 1)); sb.Append('-', cellWidth - 1);
Console.Write("+"); sb.Append('+');
} }
Console.WriteLine(); sb.AppendLine();
// Cell content // Cell content
for (int h = 0; h < cellHeight - 1; h++) for (int h = 0; h < cellHeight - 1; h++)
{ {
Console.Write(" "); sb.Append(" ");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
Console.Write("|"); sb.Append('|');
Console.Write(new string(' ', cellWidth - 1)); sb.Append(' ', cellWidth - 1);
} }
Console.WriteLine("|"); sb.AppendLine("|");
} }
} }
// Bottom border // Bottom border
Console.Write(" +"); sb.Append(" +");
for (int c = 0; c < cols; c++) for (int c = 0; c < cols; c++)
{ {
Console.Write(new string('-', cellWidth - 1)); sb.Append('-', cellWidth - 1);
Console.Write("+"); sb.Append('+');
} }
Console.WriteLine(); sb.AppendLine();
return sb.ToString();
} }
private static void DrawCanvasLayout(JsonElement zones, int refWidth, int refHeight) private static string RenderCanvasLayout(JsonElement zones, int refWidth, int refHeight)
{ {
var sb = new StringBuilder();
const int displayWidth = 49; const int displayWidth = 49;
const int displayHeight = 15; const int displayHeight = 15;
@@ -518,7 +452,6 @@ public static class LayoutVisualizer
int dw = Math.Max(3, w * displayWidth / refWidth); int dw = Math.Max(3, w * displayWidth / refWidth);
int dh = Math.Max(2, h * displayHeight / refHeight); int dh = Math.Max(2, h * displayHeight / refHeight);
// Clamp to display bounds
if (dx + dw > displayWidth) if (dx + dw > displayWidth)
{ {
dw = displayWidth - dx; dw = displayWidth - dx;
@@ -531,7 +464,6 @@ public static class LayoutVisualizer
zoneList.Add((dx, dy, dw, dh, zoneId)); zoneList.Add((dx, dy, dw, dh, zoneId));
// Fill the grid for this zone
for (int r = dy; r < dy + dh && r < displayHeight; r++) for (int r = dy; r < dy + dh && r < displayHeight; r++)
{ {
for (int c = dx; c < dx + dw && c < displayWidth; c++) for (int c = dx; c < dx + dw && c < displayWidth; c++)
@@ -544,82 +476,75 @@ public static class LayoutVisualizer
} }
// Draw top border // Draw top border
Console.Write(" +"); sb.Append(" +");
Console.Write(new string('-', displayWidth)); sb.Append('-', displayWidth);
Console.WriteLine("+"); sb.AppendLine("+");
// Draw each row // Draw each row
char[] shades = { '.', ':', '░', '▒', '▓', '█', '◆', '●', '■', '▪' };
for (int r = 0; r < displayHeight; r++) for (int r = 0; r < displayHeight; r++)
{ {
Console.Write(" |"); sb.Append(" |");
for (int c = 0; c < displayWidth; c++) for (int c = 0; c < displayWidth; c++)
{ {
var zonesHere = zoneGrid[r, c]; var zonesHere = zoneGrid[r, c];
if (zonesHere.Count == 0) if (zonesHere.Count == 0)
{ {
Console.Write(" "); sb.Append(' ');
} }
else else
{ {
// Get the topmost zone at this position
int topZone = zonesHere[zonesHere.Count - 1]; int topZone = zonesHere[zonesHere.Count - 1];
var rect = zoneList[topZone]; var rect = zoneList[topZone];
int x = rect.X; bool isTopEdge = r == rect.Y;
int y = rect.Y; bool isBottomEdge = r == rect.Y + rect.Height - 1;
int w = rect.Width; bool isLeftEdge = c == rect.X;
int h = rect.Height; bool isRightEdge = c == rect.X + rect.Width - 1;
bool isTopEdge = r == y;
bool isBottomEdge = r == y + h - 1;
bool isLeftEdge = c == x;
bool isRightEdge = c == x + w - 1;
// Draw borders
if ((isTopEdge || isBottomEdge) && (isLeftEdge || isRightEdge)) if ((isTopEdge || isBottomEdge) && (isLeftEdge || isRightEdge))
{ {
Console.Write("+"); sb.Append('+');
} }
else if (isTopEdge || isBottomEdge) else if (isTopEdge || isBottomEdge)
{ {
Console.Write("-"); sb.Append('-');
} }
else if (isLeftEdge || isRightEdge) else if (isLeftEdge || isRightEdge)
{ {
Console.Write("|"); sb.Append('|');
} }
else else
{ {
// Use shading to show different zones sb.Append(shades[topZone % shades.Length]);
char[] shades = { '.', ':', '░', '▒', '▓', '█', '◆', '●', '■', '▪' };
Console.Write(shades[topZone % shades.Length]);
} }
} }
} }
Console.WriteLine("|"); sb.AppendLine("|");
} }
// Draw bottom border // Draw bottom border
Console.Write(" +"); sb.Append(" +");
Console.Write(new string('-', displayWidth)); sb.Append('-', displayWidth);
Console.WriteLine("+"); sb.AppendLine("+");
// Draw legend // Draw legend
Console.WriteLine(); sb.AppendLine();
Console.Write(" Legend: "); sb.Append(" Legend: ");
char[] legendShades = { '.', ':', '░', '▒', '▓', '█', '◆', '●', '■', '▪' }; for (int i = 0; i < Math.Min(zoneId, shades.Length); i++)
for (int i = 0; i < Math.Min(zoneId, legendShades.Length); i++)
{ {
if (i > 0) if (i > 0)
{ {
Console.Write(", "); sb.Append(", ");
} }
Console.Write($"Zone {i} = {legendShades[i]}"); sb.Append(CultureInfo.InvariantCulture, $"Zone {i} = {shades[i]}");
} }
Console.WriteLine(); sb.AppendLine();
return sb.ToString();
} }
} }

File diff suppressed because it is too large Load Diff