Этап 2
This commit is contained in:
482
ARCHITECTURE.md
Normal file
482
ARCHITECTURE.md
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
# Minint — Architecture Document (Stage 1)
|
||||||
|
|
||||||
|
## 1. Solution Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
Minint.slnx
|
||||||
|
├── Minint.Core/ — Domain models, interfaces, pure logic (no UI, no I/O)
|
||||||
|
│ └── Minint.Core.csproj (net10.0, classlib)
|
||||||
|
├── Minint.Infrastructure/ — Binary serialization, BMP/GIF export
|
||||||
|
│ └── Minint.Infrastructure.csproj (net10.0, classlib, references Core)
|
||||||
|
├── Minint/ — Avalonia UI application
|
||||||
|
│ └── Minint.csproj (net10.0, WinExe, references Core + Infrastructure)
|
||||||
|
└── Minint.Tests/ — Unit tests (added later if needed)
|
||||||
|
└── Minint.Tests.csproj (net10.0, xunit, references Core + Infrastructure)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependency graph
|
||||||
|
|
||||||
|
```
|
||||||
|
Minint (UI) ──► Minint.Core
|
||||||
|
│
|
||||||
|
└──────► Minint.Infrastructure ──► Minint.Core
|
||||||
|
```
|
||||||
|
|
||||||
|
UI depends on Core and Infrastructure.
|
||||||
|
Infrastructure depends on Core only.
|
||||||
|
Core has zero external dependencies (pure domain).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. RAM Model (Domain Objects)
|
||||||
|
|
||||||
|
### 2.1 RgbaColor
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public readonly record struct RgbaColor(byte R, byte G, byte B, byte A)
|
||||||
|
{
|
||||||
|
public static readonly RgbaColor Transparent = new(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Value type. Equality by all four channels (record struct gives this for free).
|
||||||
|
|
||||||
|
### 2.2 MinintLayer
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public sealed class MinintLayer
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool IsVisible { get; set; }
|
||||||
|
public byte Opacity { get; set; } // 0–255, reserved for future blending
|
||||||
|
public int[] Pixels { get; } // length = Width * Height, row-major
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `Pixels[y * width + x]` = index into the parent document's palette.
|
||||||
|
- `int` chosen over `uint` because C# array indexing is int-based; max value 2³¹−1 is more than sufficient.
|
||||||
|
- On-disk packing (1–4 bytes per index) is handled only by the serializer.
|
||||||
|
|
||||||
|
### 2.3 MinintDocument
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public sealed class MinintDocument
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public uint FrameDelayMs { get; set; } // animation delay
|
||||||
|
public List<RgbaColor> Palette { get; } // shared by all layers
|
||||||
|
public List<MinintLayer> Layers { get; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Palette is per-document, shared by all layers of that document.
|
||||||
|
- Index 0 is **always** `RgbaColor.Transparent` by convention (the eraser writes 0).
|
||||||
|
- When a new document is created, the palette is initialized with at least `[Transparent]`.
|
||||||
|
|
||||||
|
### 2.4 MinintContainer
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public sealed class MinintContainer
|
||||||
|
{
|
||||||
|
public int Width { get; set; }
|
||||||
|
public int Height { get; set; }
|
||||||
|
public List<MinintDocument> Documents { get; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Width/Height at the container level: all documents and all layers share the same
|
||||||
|
dimensions. This is required for coherent animation playback and GIF export.
|
||||||
|
|
||||||
|
### 2.5 Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
MinintContainer
|
||||||
|
├─ Width, Height
|
||||||
|
└─ Documents: List<MinintDocument>
|
||||||
|
├─ Name, FrameDelayMs
|
||||||
|
├─ Palette: List<RgbaColor> ← shared by layers
|
||||||
|
└─ Layers: List<MinintLayer>
|
||||||
|
├─ Name, IsVisible, Opacity
|
||||||
|
└─ Pixels: int[] ← indices into Palette
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Binary Format `.minint`
|
||||||
|
|
||||||
|
All multi-byte integers are **little-endian**.
|
||||||
|
Strings are **UTF-8**, prefixed by a 1-byte length (max 255 chars for names).
|
||||||
|
|
||||||
|
### 3.1 Container Header (28 bytes fixed)
|
||||||
|
|
||||||
|
| Offset | Size | Type | Description |
|
||||||
|
|--------|-------|---------|--------------------------------|
|
||||||
|
| 0 | 6 | ASCII | Signature: `MININT` |
|
||||||
|
| 6 | 2 | uint16 | Format version (currently `1`) |
|
||||||
|
| 8 | 4 | uint32 | Width |
|
||||||
|
| 12 | 4 | uint32 | Height |
|
||||||
|
| 16 | 4 | uint32 | DocumentCount |
|
||||||
|
| 20 | 8 | — | Reserved (must be zero) |
|
||||||
|
|
||||||
|
### 3.2 Document Block (repeated DocumentCount times)
|
||||||
|
|
||||||
|
#### Document Header
|
||||||
|
|
||||||
|
| Offset (relative) | Size | Type | Description |
|
||||||
|
|--------------------|-------------|---------|------------------------|
|
||||||
|
| 0 | 1 | uint8 | NameLength |
|
||||||
|
| 1 | NameLength | UTF-8 | Name |
|
||||||
|
| 1+NameLength | 4 | uint32 | FrameDelayMs |
|
||||||
|
| 5+NameLength | 4 | uint32 | PaletteCount |
|
||||||
|
|
||||||
|
#### Palette (immediately follows)
|
||||||
|
|
||||||
|
`PaletteCount × 4` bytes: each color is `[R, G, B, A]` (1 byte each).
|
||||||
|
|
||||||
|
#### Index Byte Width
|
||||||
|
|
||||||
|
Derived from PaletteCount (not stored explicitly — reader computes it):
|
||||||
|
|
||||||
|
| PaletteCount | BytesPerIndex |
|
||||||
|
|----------------------|---------------|
|
||||||
|
| 1 – 255 | 1 |
|
||||||
|
| 256 – 65 535 | 2 |
|
||||||
|
| 65 536 – 16 777 215 | 3 |
|
||||||
|
| 16 777 216+ | 4 |
|
||||||
|
|
||||||
|
#### Layer Count
|
||||||
|
|
||||||
|
| Size | Type | Description |
|
||||||
|
|------|--------|-------------|
|
||||||
|
| 4 | uint32 | LayerCount |
|
||||||
|
|
||||||
|
#### Layer Block (repeated LayerCount times)
|
||||||
|
|
||||||
|
| Size | Type | Description |
|
||||||
|
|-------------------|--------|--------------------------------------|
|
||||||
|
| 1 | uint8 | LayerNameLength |
|
||||||
|
| LayerNameLength | UTF-8 | LayerName |
|
||||||
|
| 1 | uint8 | IsVisible (0 = hidden, 1 = visible) |
|
||||||
|
| 1 | uint8 | Opacity (0–255) |
|
||||||
|
| W×H×BytesPerIndex | bytes | Pixel indices, row-major, LE per idx |
|
||||||
|
|
||||||
|
### 3.3 Validation Rules
|
||||||
|
|
||||||
|
1. Signature must be exactly `MININT`.
|
||||||
|
2. Version must be `1` (future-proof: reject unknown versions).
|
||||||
|
3. Width, Height ≥ 1.
|
||||||
|
4. DocumentCount ≥ 1.
|
||||||
|
5. PaletteCount ≥ 1 (at least the transparent color).
|
||||||
|
6. Every pixel index must be `< PaletteCount`.
|
||||||
|
7. Reserved bytes must be zero (warn on non-zero for forward compat).
|
||||||
|
8. Unexpected EOF → error with clear message.
|
||||||
|
|
||||||
|
### 3.4 Format Size Estimate
|
||||||
|
|
||||||
|
For a 64×64 image, 1 document, 1 layer, 16-color palette:
|
||||||
|
- Header: 28 bytes
|
||||||
|
- Doc header: ~10 bytes
|
||||||
|
- Palette: 16 × 4 = 64 bytes
|
||||||
|
- Layer header: ~8 bytes
|
||||||
|
- Pixel data: 64 × 64 × 1 = 4 096 bytes
|
||||||
|
- **Total ≈ 4.2 KB**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Classes, Interfaces, and Services
|
||||||
|
|
||||||
|
### 4.1 Core — Domain Models
|
||||||
|
|
||||||
|
| Type | Kind | Responsibility |
|
||||||
|
|---------------------|---------------|-----------------------------------------|
|
||||||
|
| `RgbaColor` | record struct | Immutable RGBA color value |
|
||||||
|
| `MinintLayer` | class | Single raster layer (index buffer) |
|
||||||
|
| `MinintDocument` | class | Document with palette + layers |
|
||||||
|
| `MinintContainer` | class | Top-level container of documents |
|
||||||
|
|
||||||
|
### 4.2 Core — Interfaces
|
||||||
|
|
||||||
|
| Interface | Methods (key) |
|
||||||
|
|------------------------|--------------------------------------------------|
|
||||||
|
| `ICompositor` | `uint[] Composite(MinintDocument, int w, int h)` |
|
||||||
|
| `IPaletteService` | `int EnsureColor(doc, RgbaColor)`, `void CompactPalette(doc)` |
|
||||||
|
| `IDrawingService` | `void ApplyBrush(layer, x, y, radius, colorIdx, w, h)` |
|
||||||
|
| `IFloodFillService` | `void Fill(layer, x, y, newColorIdx, w, h)` |
|
||||||
|
| `IImageEffectService` | `void AdjustContrast(doc, float factor)`, `void ToGrayscale(doc)` |
|
||||||
|
| `IPatternGenerator` | `MinintDocument Generate(PatternParams)` |
|
||||||
|
| `IFragmentService` | `void CopyFragment(src, dst, rect, destPoint)` |
|
||||||
|
|
||||||
|
### 4.3 Core — Service Implementations
|
||||||
|
|
||||||
|
| Class | Implements | Notes |
|
||||||
|
|------------------------|-------------------------|------------------------------------|
|
||||||
|
| `Compositor` | `ICompositor` | Alpha-blends layers bottom→top |
|
||||||
|
| `PaletteService` | `IPaletteService` | Color lookup, add, compact |
|
||||||
|
| `DrawingService` | `IDrawingService` | Circle mask brush/eraser |
|
||||||
|
| `FloodFillService` | `IFloodFillService` | BFS flood fill on index array |
|
||||||
|
| `ImageEffectService` | `IImageEffectService` | Palette-based contrast/grayscale |
|
||||||
|
| `PatternGenerator` | `IPatternGenerator` | Checkerboard, gradient, stripes… |
|
||||||
|
| `FragmentService` | `IFragmentService` | Copy rect with palette merging |
|
||||||
|
|
||||||
|
### 4.4 Infrastructure
|
||||||
|
|
||||||
|
| Class | Responsibility |
|
||||||
|
|-------------------------|---------------------------------------------------|
|
||||||
|
| `MinintSerializer` | Read/write `.minint` binary format |
|
||||||
|
| `BmpExporter` | Export composited document to 32-bit BMP |
|
||||||
|
| `GifExporter` | Export all documents as animated GIF (uses lib) |
|
||||||
|
|
||||||
|
### 4.5 UI (Avalonia) — Key Types
|
||||||
|
|
||||||
|
| Type | Kind | Responsibility |
|
||||||
|
|----------------------------|------------|----------------------------------------|
|
||||||
|
| `MainWindowViewModel` | ViewModel | Orchestrates the editor |
|
||||||
|
| `EditorViewModel` | ViewModel | Current document, tool state, viewport |
|
||||||
|
| `DocumentListViewModel` | ViewModel | Document tabs / frame list |
|
||||||
|
| `LayerListViewModel` | ViewModel | Layer panel |
|
||||||
|
| `ToolSettingsViewModel` | ViewModel | Brush size, color, tool type |
|
||||||
|
| `PixelCanvas` | Control | Custom Avalonia control for rendering |
|
||||||
|
| `Viewport` | Model | Zoom, offset, coord transforms |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Key Algorithms
|
||||||
|
|
||||||
|
### 5.1 Compositing (layers → RGBA buffer)
|
||||||
|
|
||||||
|
```
|
||||||
|
result = new uint[W * H] (RGBA packed as uint, initialized to transparent)
|
||||||
|
|
||||||
|
for each layer (bottom → top):
|
||||||
|
if !layer.IsVisible: skip
|
||||||
|
for each pixel i in 0..W*H-1:
|
||||||
|
srcColor = palette[layer.Pixels[i]]
|
||||||
|
effectiveAlpha = srcColor.A * layer.Opacity / 255
|
||||||
|
if effectiveAlpha == 0: continue
|
||||||
|
result[i] = AlphaBlend(result[i], srcColor with A=effectiveAlpha)
|
||||||
|
```
|
||||||
|
|
||||||
|
Alpha blending (standard "over" operator):
|
||||||
|
```
|
||||||
|
outA = srcA + dstA * (255 - srcA) / 255
|
||||||
|
outR = (srcR * srcA + dstR * dstA * (255 - srcA) / 255) / outA
|
||||||
|
(same for G, B)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Brush / Eraser
|
||||||
|
|
||||||
|
Circle mask: for each pixel (px, py) within bounding box of (cx ± r, cy ± r):
|
||||||
|
```
|
||||||
|
if (px - cx)² + (py - cy)² <= r²:
|
||||||
|
mark pixel as affected
|
||||||
|
```
|
||||||
|
|
||||||
|
- Brush: writes `colorIndex` to affected pixels.
|
||||||
|
- Eraser: writes `0` (transparent index) to affected pixels.
|
||||||
|
- Preview: compute mask, overlay on canvas as semi-transparent highlight without modifying pixel data.
|
||||||
|
|
||||||
|
### 5.3 Flood Fill (BFS)
|
||||||
|
|
||||||
|
```
|
||||||
|
targetIdx = layer.Pixels[y * W + x]
|
||||||
|
if targetIdx == fillIdx: return
|
||||||
|
queue = { (x, y) }
|
||||||
|
visited = bitset of W * H
|
||||||
|
|
||||||
|
while queue not empty:
|
||||||
|
(cx, cy) = dequeue
|
||||||
|
if out of bounds or visited: continue
|
||||||
|
if layer.Pixels[cy * W + cx] != targetIdx: continue
|
||||||
|
mark visited
|
||||||
|
layer.Pixels[cy * W + cx] = fillIdx
|
||||||
|
enqueue 4-neighbors
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Palette Compaction
|
||||||
|
|
||||||
|
On save (or on demand):
|
||||||
|
1. Scan all layers, collect used indices into a `HashSet<int>`.
|
||||||
|
2. Build new palette = only used colors (keep index 0 = transparent).
|
||||||
|
3. Build old→new index mapping.
|
||||||
|
4. Remap all pixel arrays.
|
||||||
|
|
||||||
|
### 5.5 Copy Fragment Between Documents (A4)
|
||||||
|
|
||||||
|
```
|
||||||
|
for each pixel (sx, sy) in source rect:
|
||||||
|
srcIdx = srcLayer.Pixels[sy * W + sx]
|
||||||
|
srcColor = srcDoc.Palette[srcIdx]
|
||||||
|
dstIdx = dstDoc.PaletteService.EnsureColor(srcColor)
|
||||||
|
dstLayer.Pixels[destY * W + destX] = dstIdx
|
||||||
|
destX++; ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`EnsureColor` checks if color exists in target palette; if not, appends it and returns the new index.
|
||||||
|
|
||||||
|
### 5.6 Contrast (A1)
|
||||||
|
|
||||||
|
Applied to palette (not individual pixels):
|
||||||
|
```
|
||||||
|
for each color in palette (skip index 0 transparent):
|
||||||
|
R' = clamp(factor * (R - 128) + 128, 0, 255)
|
||||||
|
G' = clamp(factor * (G - 128) + 128, 0, 255)
|
||||||
|
B' = clamp(factor * (B - 128) + 128, 0, 255)
|
||||||
|
A' = A (unchanged)
|
||||||
|
```
|
||||||
|
|
||||||
|
`factor > 1` = increase contrast, `factor < 1` = decrease.
|
||||||
|
|
||||||
|
This is correct because all pixels reference the palette; modifying palette entries
|
||||||
|
globally changes the image. No pixel remapping needed.
|
||||||
|
|
||||||
|
**Caveat**: if two palette entries become identical after transformation, they could
|
||||||
|
be merged (optional optimization).
|
||||||
|
|
||||||
|
### 5.7 Grayscale (A2)
|
||||||
|
|
||||||
|
Applied to palette:
|
||||||
|
```
|
||||||
|
for each color in palette (skip index 0 transparent):
|
||||||
|
gray = (byte)(0.299 * R + 0.587 * G + 0.114 * B)
|
||||||
|
color = RgbaColor(gray, gray, gray, A)
|
||||||
|
```
|
||||||
|
|
||||||
|
Uses ITU-R BT.601 luminance formula (standard for perceptual grayscale).
|
||||||
|
|
||||||
|
Same caveat about duplicate entries applies.
|
||||||
|
|
||||||
|
### 5.8 BMP Export
|
||||||
|
|
||||||
|
1. Composite document into RGBA buffer.
|
||||||
|
2. Write BMP file header (14 bytes) + DIB BITMAPINFOHEADER (40 bytes).
|
||||||
|
3. Write pixel data as 32-bit BGRA, bottom-up row order.
|
||||||
|
4. No compression, no palette section (direct 32-bit).
|
||||||
|
|
||||||
|
### 5.9 GIF Export
|
||||||
|
|
||||||
|
1. For each document in container, composite into RGBA buffer.
|
||||||
|
2. Use external library (e.g. `SixLabors.ImageSharp`) to encode frames
|
||||||
|
with per-frame delay from `FrameDelayMs`.
|
||||||
|
3. Output as animated GIF.
|
||||||
|
|
||||||
|
Only the GIF encoder is external; the `.minint` format is fully self-implemented.
|
||||||
|
|
||||||
|
### 5.10 Pattern Generation (Б4)
|
||||||
|
|
||||||
|
Factory-based: `PatternType` enum + parameters struct per pattern type.
|
||||||
|
|
||||||
|
| Pattern | Parameters |
|
||||||
|
|----------------------|-------------------------------------|
|
||||||
|
| Checkerboard | cellSize, color1, color2 |
|
||||||
|
| Gradient (H/V) | startColor, endColor, direction |
|
||||||
|
| Stripes (H/V) | stripeWidth, color1, color2 |
|
||||||
|
| Concentric Circles | centerX, centerY, ringWidth, colors |
|
||||||
|
| Tile | tileWidth, tileHeight, colors |
|
||||||
|
|
||||||
|
Each generator creates a `MinintDocument` with an appropriate palette and a single layer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Viewport and Canvas Design
|
||||||
|
|
||||||
|
```
|
||||||
|
Viewport:
|
||||||
|
Zoom: double (1.0 = 1 pixel = 1 screen pixel)
|
||||||
|
OffsetX, OffsetY: double (pan position)
|
||||||
|
|
||||||
|
ScreenToPixel(screenX, screenY) → (pixelX, pixelY)
|
||||||
|
pixelX = (int)floor((screenX - OffsetX) / Zoom)
|
||||||
|
pixelY = (int)floor((screenY - OffsetY) / Zoom)
|
||||||
|
|
||||||
|
PixelToScreen(pixelX, pixelY) → (screenX, screenY)
|
||||||
|
screenX = pixelX * Zoom + OffsetX
|
||||||
|
screenY = pixelY * Zoom + OffsetY
|
||||||
|
```
|
||||||
|
|
||||||
|
Canvas (`PixelCanvas` custom control):
|
||||||
|
- Renders composited bitmap using `DrawImage` with `BitmapInterpolationMode.None` (nearest-neighbor).
|
||||||
|
- Overlays pixel grid (vertical + horizontal lines) when zoom > threshold and grid enabled.
|
||||||
|
- Overlays brush/eraser preview as semi-transparent mask.
|
||||||
|
- Handles mouse events: wheel → zoom, middle-drag → pan, left-click/drag → tool action.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Key Decisions and Trade-offs
|
||||||
|
|
||||||
|
### D1. Width/Height at container level
|
||||||
|
|
||||||
|
**Chosen**: container-level (all documents share dimensions).
|
||||||
|
**Rationale**: required for coherent animation; GIF frames must be same size.
|
||||||
|
**Trade-off**: cannot mix different-sized documents in one container.
|
||||||
|
|
||||||
|
### D2. int[] for pixel indices in RAM
|
||||||
|
|
||||||
|
**Chosen**: `int[]` (4 bytes per pixel in memory).
|
||||||
|
**Rationale**: simple, fast, native array indexing; on-disk packing handles storage efficiency.
|
||||||
|
**Trade-off**: memory usage is 4× compared to byte[] for small palettes, but acceptable for typical image sizes (e.g., 1024×1024 = 4 MB).
|
||||||
|
|
||||||
|
### D3. Transparent color at palette index 0
|
||||||
|
|
||||||
|
**Chosen**: convention, always enforced.
|
||||||
|
**Rationale**: simplifies eraser (write 0), new layer initialization (fill with 0), and compositing (skip transparent early).
|
||||||
|
**Trade-off**: first palette slot is reserved; user cannot reassign it.
|
||||||
|
|
||||||
|
### D4. Grayscale / contrast via palette transformation
|
||||||
|
|
||||||
|
**Chosen**: modify palette entries directly.
|
||||||
|
**Rationale**: elegant — all pixels update automatically; no per-pixel remapping.
|
||||||
|
**Trade-off**: operation is document-wide (affects all layers). Per-layer effects would require palette duplication or a different approach. For this project scope, document-wide is acceptable and explicitly chosen.
|
||||||
|
|
||||||
|
### D5. Layer opacity in compositing
|
||||||
|
|
||||||
|
**Chosen**: multiply palette alpha by layer opacity byte.
|
||||||
|
**Rationale**: opens path for future layer blending without breaking anything now.
|
||||||
|
**Default**: new layers have `Opacity = 255` (fully opaque).
|
||||||
|
|
||||||
|
### D6. Eraser semantics
|
||||||
|
|
||||||
|
**Chosen**: eraser writes index 0 (transparent).
|
||||||
|
**Rationale**: consistent with palette convention; compositing naturally handles it.
|
||||||
|
|
||||||
|
### D7. GIF export library
|
||||||
|
|
||||||
|
**Chosen**: external library for GIF encoding only (`SixLabors.ImageSharp` or equivalent).
|
||||||
|
**Rationale**: GIF LZW compression is complex and not the focus of this project.
|
||||||
|
**Constraint**: `.minint` serialization remains fully self-implemented.
|
||||||
|
|
||||||
|
### D8. No undo/redo in initial implementation
|
||||||
|
|
||||||
|
**Chosen**: not implemented in first pass; architecture does not prevent it.
|
||||||
|
**Path forward**: command pattern or snapshot-based undo can be layered on later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Staged Implementation Plan
|
||||||
|
|
||||||
|
| Stage | Scope |
|
||||||
|
|-------|----------------------------------------------------|
|
||||||
|
| 1 | ✅ Architecture & design (this document) |
|
||||||
|
| 2 | Solution scaffold + domain models |
|
||||||
|
| 3 | Binary `.minint` serialization + round-trip test |
|
||||||
|
| 4 | Compositing + palette management + RGBA buffer |
|
||||||
|
| 5 | Basic Avalonia UI (main window, menus, panels) |
|
||||||
|
| 6 | Canvas: pan, zoom, grid, nearest-neighbor |
|
||||||
|
| 7 | Drawing tools: brush, eraser, flood fill, preview |
|
||||||
|
| 8 | Layer & document management UI |
|
||||||
|
| 9 | Effects: contrast, grayscale, fragment copy, patterns |
|
||||||
|
| 10 | Animation playback + BMP/GIF export |
|
||||||
|
| 11 | Polish, tests, documentation |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. External Dependencies
|
||||||
|
|
||||||
|
| Package | Project | Purpose |
|
||||||
|
|---------------------------------|----------------|-----------------------|
|
||||||
|
| Avalonia 11.3.8 | Minint (UI) | UI framework |
|
||||||
|
| Avalonia.Desktop 11.3.8 | Minint (UI) | Desktop host |
|
||||||
|
| Avalonia.Themes.Fluent 11.3.8 | Minint (UI) | Theme |
|
||||||
|
| CommunityToolkit.Mvvm 8.2.1 | Minint (UI) | MVVM helpers |
|
||||||
|
| SixLabors.ImageSharp (TBD) | Infrastructure | GIF export only |
|
||||||
|
|
||||||
|
Core project: **zero** external dependencies.
|
||||||
9
Minint.Core/Minint.Core.csproj
Normal file
9
Minint.Core/Minint.Core.csproj
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
35
Minint.Core/Models/MinintContainer.cs
Normal file
35
Minint.Core/Models/MinintContainer.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
namespace Minint.Core.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Top-level container: holds dimensions shared by all documents/layers,
|
||||||
|
/// and a list of documents (frames).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MinintContainer
|
||||||
|
{
|
||||||
|
public int Width { get; set; }
|
||||||
|
public int Height { get; set; }
|
||||||
|
public List<MinintDocument> Documents { get; }
|
||||||
|
|
||||||
|
public int PixelCount => Width * Height;
|
||||||
|
|
||||||
|
public MinintContainer(int width, int height)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfLessThan(width, 1);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfLessThan(height, 1);
|
||||||
|
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
Documents = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new document with a single transparent layer and adds it to the container.
|
||||||
|
/// </summary>
|
||||||
|
public MinintDocument AddNewDocument(string name)
|
||||||
|
{
|
||||||
|
var doc = new MinintDocument(name);
|
||||||
|
doc.Layers.Add(new MinintLayer("Layer 1", PixelCount));
|
||||||
|
Documents.Add(doc);
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Minint.Core/Models/MinintDocument.cs
Normal file
53
Minint.Core/Models/MinintDocument.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
namespace Minint.Core.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A single document (frame) within a container.
|
||||||
|
/// Has its own palette shared by all layers, plus a list of layers.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MinintDocument
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delay before showing the next frame during animation playback (ms).
|
||||||
|
/// </summary>
|
||||||
|
public uint FrameDelayMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Document palette. Index 0 is always <see cref="RgbaColor.Transparent"/>.
|
||||||
|
/// All layers reference colors by index into this list.
|
||||||
|
/// </summary>
|
||||||
|
public List<RgbaColor> Palette { get; }
|
||||||
|
|
||||||
|
public List<MinintLayer> Layers { get; }
|
||||||
|
|
||||||
|
public MinintDocument(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
FrameDelayMs = 100;
|
||||||
|
Palette = [RgbaColor.Transparent];
|
||||||
|
Layers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for deserialization — accepts pre-built palette and layers.
|
||||||
|
/// </summary>
|
||||||
|
public MinintDocument(string name, uint frameDelayMs, List<RgbaColor> palette, List<MinintLayer> layers)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
FrameDelayMs = frameDelayMs;
|
||||||
|
Palette = palette;
|
||||||
|
Layers = layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the number of bytes needed to store a single palette index on disk.
|
||||||
|
/// </summary>
|
||||||
|
public int IndexByteWidth => Palette.Count switch
|
||||||
|
{
|
||||||
|
<= 255 => 1,
|
||||||
|
<= 65_535 => 2,
|
||||||
|
<= 16_777_215 => 3,
|
||||||
|
_ => 4
|
||||||
|
};
|
||||||
|
}
|
||||||
42
Minint.Core/Models/MinintLayer.cs
Normal file
42
Minint.Core/Models/MinintLayer.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
namespace Minint.Core.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A single raster layer. Pixels are indices into the parent document's palette.
|
||||||
|
/// Array layout is row-major: Pixels[y * width + x].
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MinintLayer
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool IsVisible { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per-layer opacity (0 = fully transparent, 255 = fully opaque).
|
||||||
|
/// Used during compositing: effective alpha = paletteColor.A * Opacity / 255.
|
||||||
|
/// </summary>
|
||||||
|
public byte Opacity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Palette indices, length must equal container Width * Height.
|
||||||
|
/// Index 0 = transparent by convention.
|
||||||
|
/// </summary>
|
||||||
|
public int[] Pixels { get; }
|
||||||
|
|
||||||
|
public MinintLayer(string name, int pixelCount)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
IsVisible = true;
|
||||||
|
Opacity = 255;
|
||||||
|
Pixels = new int[pixelCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor for deserialization — accepts a pre-filled pixel buffer.
|
||||||
|
/// </summary>
|
||||||
|
public MinintLayer(string name, bool isVisible, byte opacity, int[] pixels)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
IsVisible = isVisible;
|
||||||
|
Opacity = opacity;
|
||||||
|
Pixels = pixels;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Minint.Core/Models/RgbaColor.cs
Normal file
37
Minint.Core/Models/RgbaColor.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Minint.Core.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 4-byte RGBA color value. Equality is component-wise.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public readonly record struct RgbaColor(byte R, byte G, byte B, byte A)
|
||||||
|
{
|
||||||
|
public static readonly RgbaColor Transparent = new(0, 0, 0, 0);
|
||||||
|
public static readonly RgbaColor Black = new(0, 0, 0, 255);
|
||||||
|
public static readonly RgbaColor White = new(255, 255, 255, 255);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Packs color into a single uint as 0xAABBGGRR (little-endian RGBA).
|
||||||
|
/// Suitable for writing directly into BGRA bitmap buffers after byte-swap,
|
||||||
|
/// or for use as a dictionary key.
|
||||||
|
/// </summary>
|
||||||
|
public uint ToPackedRgba() =>
|
||||||
|
(uint)(R | (G << 8) | (B << 16) | (A << 24));
|
||||||
|
|
||||||
|
public static RgbaColor FromPackedRgba(uint packed) =>
|
||||||
|
new(
|
||||||
|
(byte)(packed & 0xFF),
|
||||||
|
(byte)((packed >> 8) & 0xFF),
|
||||||
|
(byte)((packed >> 16) & 0xFF),
|
||||||
|
(byte)((packed >> 24) & 0xFF));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Packs as 0xAARRGGBB — used for Avalonia/SkiaSharp pixel buffers.
|
||||||
|
/// </summary>
|
||||||
|
public uint ToPackedArgb() =>
|
||||||
|
(uint)(B | (G << 8) | (R << 16) | (A << 24));
|
||||||
|
|
||||||
|
public override string ToString() => $"#{R:X2}{G:X2}{B:X2}{A:X2}";
|
||||||
|
}
|
||||||
13
Minint.Core/Services/IBmpExporter.cs
Normal file
13
Minint.Core/Services/IBmpExporter.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface IBmpExporter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Exports a composited ARGB pixel buffer as a 32-bit BMP file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Output stream.</param>
|
||||||
|
/// <param name="pixels">Pixel data packed as 0xAARRGGBB, row-major.</param>
|
||||||
|
/// <param name="width">Image width.</param>
|
||||||
|
/// <param name="height">Image height.</param>
|
||||||
|
void Export(Stream stream, uint[] pixels, int width, int height);
|
||||||
|
}
|
||||||
13
Minint.Core/Services/ICompositor.cs
Normal file
13
Minint.Core/Services/ICompositor.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface ICompositor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Composites all visible layers of <paramref name="document"/> into a flat RGBA buffer.
|
||||||
|
/// Result is packed as ARGB (0xAARRGGBB) per pixel, row-major, length = width * height.
|
||||||
|
/// Layers are blended bottom-to-top with alpha compositing.
|
||||||
|
/// </summary>
|
||||||
|
uint[] Composite(MinintDocument document, int width, int height);
|
||||||
|
}
|
||||||
25
Minint.Core/Services/IDrawingService.cs
Normal file
25
Minint.Core/Services/IDrawingService.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface IDrawingService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a circular brush stroke at (<paramref name="cx"/>, <paramref name="cy"/>)
|
||||||
|
/// with the given <paramref name="radius"/>. Sets affected pixels to <paramref name="colorIndex"/>.
|
||||||
|
/// </summary>
|
||||||
|
void ApplyBrush(MinintLayer layer, int cx, int cy, int radius, int colorIndex, int width, int height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a circular eraser at (<paramref name="cx"/>, <paramref name="cy"/>)
|
||||||
|
/// with the given <paramref name="radius"/>. Sets affected pixels to index 0 (transparent).
|
||||||
|
/// </summary>
|
||||||
|
void ApplyEraser(MinintLayer layer, int cx, int cy, int radius, int width, int height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the set of pixel coordinates affected by a circular brush/eraser
|
||||||
|
/// centered at (<paramref name="cx"/>, <paramref name="cy"/>) with given <paramref name="radius"/>.
|
||||||
|
/// Used for tool preview overlay.
|
||||||
|
/// </summary>
|
||||||
|
List<(int X, int Y)> GetBrushMask(int cx, int cy, int radius, int width, int height);
|
||||||
|
}
|
||||||
12
Minint.Core/Services/IFloodFillService.cs
Normal file
12
Minint.Core/Services/IFloodFillService.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface IFloodFillService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Flood-fills a contiguous region of identical color starting at (<paramref name="x"/>, <paramref name="y"/>)
|
||||||
|
/// with <paramref name="newColorIndex"/>. Uses 4-connectivity (up/down/left/right).
|
||||||
|
/// </summary>
|
||||||
|
void Fill(MinintLayer layer, int x, int y, int newColorIndex, int width, int height);
|
||||||
|
}
|
||||||
29
Minint.Core/Services/IFragmentService.cs
Normal file
29
Minint.Core/Services/IFragmentService.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface IFragmentService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Copies a rectangular region from one document/layer to another.
|
||||||
|
/// Palette colors are merged: missing colors are added to the destination palette.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="srcDoc">Source document.</param>
|
||||||
|
/// <param name="srcLayerIndex">Index of the source layer.</param>
|
||||||
|
/// <param name="srcX">Source rectangle X origin.</param>
|
||||||
|
/// <param name="srcY">Source rectangle Y origin.</param>
|
||||||
|
/// <param name="regionWidth">Width of the region to copy.</param>
|
||||||
|
/// <param name="regionHeight">Height of the region to copy.</param>
|
||||||
|
/// <param name="dstDoc">Destination document.</param>
|
||||||
|
/// <param name="dstLayerIndex">Index of the destination layer.</param>
|
||||||
|
/// <param name="dstX">Destination X origin.</param>
|
||||||
|
/// <param name="dstY">Destination Y origin.</param>
|
||||||
|
/// <param name="containerWidth">Container width (shared by both docs).</param>
|
||||||
|
/// <param name="containerHeight">Container height (shared by both docs).</param>
|
||||||
|
void CopyFragment(
|
||||||
|
MinintDocument srcDoc, int srcLayerIndex,
|
||||||
|
int srcX, int srcY, int regionWidth, int regionHeight,
|
||||||
|
MinintDocument dstDoc, int dstLayerIndex,
|
||||||
|
int dstX, int dstY,
|
||||||
|
int containerWidth, int containerHeight);
|
||||||
|
}
|
||||||
13
Minint.Core/Services/IGifExporter.cs
Normal file
13
Minint.Core/Services/IGifExporter.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface IGifExporter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Exports multiple frames as an animated GIF.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Output stream.</param>
|
||||||
|
/// <param name="frames">Sequence of (ARGB pixels, delay in ms) per frame.</param>
|
||||||
|
/// <param name="width">Frame width.</param>
|
||||||
|
/// <param name="height">Frame height.</param>
|
||||||
|
void Export(Stream stream, IReadOnlyList<(uint[] Pixels, uint DelayMs)> frames, int width, int height);
|
||||||
|
}
|
||||||
20
Minint.Core/Services/IImageEffectService.cs
Normal file
20
Minint.Core/Services/IImageEffectService.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface IImageEffectService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adjusts contrast of the document by modifying palette colors.
|
||||||
|
/// <paramref name="factor"/> > 1 increases contrast, < 1 decreases.
|
||||||
|
/// Index 0 (transparent) is not modified.
|
||||||
|
/// </summary>
|
||||||
|
void AdjustContrast(MinintDocument document, float factor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the document to grayscale by modifying palette colors.
|
||||||
|
/// Uses ITU-R BT.601 luminance: gray = 0.299R + 0.587G + 0.114B.
|
||||||
|
/// Index 0 (transparent) is not modified.
|
||||||
|
/// </summary>
|
||||||
|
void ToGrayscale(MinintDocument document);
|
||||||
|
}
|
||||||
17
Minint.Core/Services/IMinintSerializer.cs
Normal file
17
Minint.Core/Services/IMinintSerializer.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface IMinintSerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes the container to a binary .minint stream.
|
||||||
|
/// </summary>
|
||||||
|
void Write(Stream stream, MinintContainer container);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializes a .minint stream into a container.
|
||||||
|
/// Throws <see cref="InvalidDataException"/> on format/validation errors.
|
||||||
|
/// </summary>
|
||||||
|
MinintContainer Read(Stream stream);
|
||||||
|
}
|
||||||
23
Minint.Core/Services/IPaletteService.cs
Normal file
23
Minint.Core/Services/IPaletteService.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface IPaletteService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the index of <paramref name="color"/> in the document palette.
|
||||||
|
/// If the color is not present, appends it and returns the new index.
|
||||||
|
/// </summary>
|
||||||
|
int EnsureColor(MinintDocument document, RgbaColor color);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds index of an exact color match, or returns -1 if not found.
|
||||||
|
/// </summary>
|
||||||
|
int FindColor(MinintDocument document, RgbaColor color);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes unused colors from the palette and remaps all layer pixel indices.
|
||||||
|
/// Index 0 (transparent) is always preserved.
|
||||||
|
/// </summary>
|
||||||
|
void CompactPalette(MinintDocument document);
|
||||||
|
}
|
||||||
28
Minint.Core/Services/IPatternGenerator.cs
Normal file
28
Minint.Core/Services/IPatternGenerator.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public enum PatternType
|
||||||
|
{
|
||||||
|
Checkerboard,
|
||||||
|
HorizontalGradient,
|
||||||
|
VerticalGradient,
|
||||||
|
HorizontalStripes,
|
||||||
|
VerticalStripes,
|
||||||
|
ConcentricCircles,
|
||||||
|
Tile
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPatternGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a new document with a single layer filled with the specified pattern.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Pattern type.</param>
|
||||||
|
/// <param name="width">Image width in pixels.</param>
|
||||||
|
/// <param name="height">Image height in pixels.</param>
|
||||||
|
/// <param name="colors">Colors to use (interpretation depends on pattern type).</param>
|
||||||
|
/// <param name="param1">Primary parameter: cell/stripe size, ring width, etc.</param>
|
||||||
|
/// <param name="param2">Secondary parameter (optional, pattern-dependent).</param>
|
||||||
|
MinintDocument Generate(PatternType type, int width, int height, RgbaColor[] colors, int param1, int param2 = 0);
|
||||||
|
}
|
||||||
13
Minint.Infrastructure/Minint.Infrastructure.csproj
Normal file
13
Minint.Infrastructure/Minint.Infrastructure.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Minint.Core\Minint.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
|
<Project Path="Minint.Core/Minint.Core.csproj" />
|
||||||
|
<Project Path="Minint.Infrastructure/Minint.Infrastructure.csproj" />
|
||||||
<Project Path="Minint/Minint.csproj" />
|
<Project Path="Minint/Minint.csproj" />
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -9,16 +9,19 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Models\" />
|
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Minint.Core\Minint.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Minint.Infrastructure\Minint.Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="11.3.8" />
|
<PackageReference Include="Avalonia" Version="11.3.8" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.8" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.8" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.8" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.8" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.8" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.8" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.8">
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.8">
|
||||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
|
|||||||
Reference in New Issue
Block a user