Этап 9
This commit is contained in:
18
Minint.Core/Services/IImageEffectsService.cs
Normal file
18
Minint.Core/Services/IImageEffectsService.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services;
|
||||||
|
|
||||||
|
public interface IImageEffectsService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adjusts contrast of the document by transforming its palette colors.
|
||||||
|
/// <paramref name="factor"/> of 0 = all gray, 1 = no change, >1 = increased contrast.
|
||||||
|
/// </summary>
|
||||||
|
void ApplyContrast(MinintDocument doc, double factor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the document to grayscale by transforming its palette colors
|
||||||
|
/// using the luminance formula: 0.299R + 0.587G + 0.114B.
|
||||||
|
/// </summary>
|
||||||
|
void ApplyGrayscale(MinintDocument doc);
|
||||||
|
}
|
||||||
38
Minint.Core/Services/Impl/ImageEffectsService.cs
Normal file
38
Minint.Core/Services/Impl/ImageEffectsService.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services.Impl;
|
||||||
|
|
||||||
|
public sealed class ImageEffectsService : IImageEffectsService
|
||||||
|
{
|
||||||
|
public void ApplyContrast(MinintDocument doc, double factor)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < doc.Palette.Count; i++)
|
||||||
|
{
|
||||||
|
var c = doc.Palette[i];
|
||||||
|
doc.Palette[i] = new RgbaColor(
|
||||||
|
ContrastByte(c.R, factor),
|
||||||
|
ContrastByte(c.G, factor),
|
||||||
|
ContrastByte(c.B, factor),
|
||||||
|
c.A);
|
||||||
|
}
|
||||||
|
doc.InvalidatePaletteCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyGrayscale(MinintDocument doc)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < doc.Palette.Count; i++)
|
||||||
|
{
|
||||||
|
var c = doc.Palette[i];
|
||||||
|
byte gray = (byte)Math.Clamp((int)(0.299 * c.R + 0.587 * c.G + 0.114 * c.B + 0.5), 0, 255);
|
||||||
|
doc.Palette[i] = new RgbaColor(gray, gray, gray, c.A);
|
||||||
|
}
|
||||||
|
doc.InvalidatePaletteCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte ContrastByte(byte value, double factor)
|
||||||
|
{
|
||||||
|
double v = ((value / 255.0) - 0.5) * factor + 0.5;
|
||||||
|
return (byte)Math.Clamp((int)(v * 255 + 0.5), 0, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
113
Minint.Core/Services/Impl/PatternGenerator.cs
Normal file
113
Minint.Core/Services/Impl/PatternGenerator.cs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
using System;
|
||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Core.Services.Impl;
|
||||||
|
|
||||||
|
public sealed class PatternGenerator : IPatternGenerator
|
||||||
|
{
|
||||||
|
public MinintDocument Generate(PatternType type, int width, int height, RgbaColor[] colors, int param1, int param2 = 0)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfLessThan(width, 1);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfLessThan(height, 1);
|
||||||
|
if (colors.Length < 2)
|
||||||
|
throw new ArgumentException("At least two colors are required.", nameof(colors));
|
||||||
|
|
||||||
|
var doc = new MinintDocument($"Pattern ({type})");
|
||||||
|
var layer = new MinintLayer("Pattern", width * height);
|
||||||
|
doc.Layers.Add(layer);
|
||||||
|
|
||||||
|
int[] colorIndices = new int[colors.Length];
|
||||||
|
for (int i = 0; i < colors.Length; i++)
|
||||||
|
colorIndices[i] = doc.EnsureColorCached(colors[i]);
|
||||||
|
|
||||||
|
int cellSize = Math.Max(param1, 1);
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case PatternType.Checkerboard:
|
||||||
|
FillCheckerboard(layer.Pixels, width, height, colorIndices, cellSize);
|
||||||
|
break;
|
||||||
|
case PatternType.HorizontalGradient:
|
||||||
|
FillGradient(layer.Pixels, width, height, colors[0], colors[1], doc, horizontal: true);
|
||||||
|
break;
|
||||||
|
case PatternType.VerticalGradient:
|
||||||
|
FillGradient(layer.Pixels, width, height, colors[0], colors[1], doc, horizontal: false);
|
||||||
|
break;
|
||||||
|
case PatternType.HorizontalStripes:
|
||||||
|
FillStripes(layer.Pixels, width, height, colorIndices, cellSize, horizontal: true);
|
||||||
|
break;
|
||||||
|
case PatternType.VerticalStripes:
|
||||||
|
FillStripes(layer.Pixels, width, height, colorIndices, cellSize, horizontal: false);
|
||||||
|
break;
|
||||||
|
case PatternType.ConcentricCircles:
|
||||||
|
FillCircles(layer.Pixels, width, height, colorIndices, cellSize);
|
||||||
|
break;
|
||||||
|
case PatternType.Tile:
|
||||||
|
FillTile(layer.Pixels, width, height, colorIndices, cellSize, Math.Max(param2, 1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillCheckerboard(int[] pixels, int w, int h, int[] ci, int cell)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < h; y++)
|
||||||
|
for (int x = 0; x < w; x++)
|
||||||
|
pixels[y * w + x] = ci[((x / cell) + (y / cell)) % 2 == 0 ? 0 : 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillGradient(int[] pixels, int w, int h, RgbaColor c0, RgbaColor c1,
|
||||||
|
MinintDocument doc, bool horizontal)
|
||||||
|
{
|
||||||
|
int steps = horizontal ? w : h;
|
||||||
|
for (int s = 0; s < steps; s++)
|
||||||
|
{
|
||||||
|
double t = steps > 1 ? (double)s / (steps - 1) : 0;
|
||||||
|
var c = new RgbaColor(
|
||||||
|
Lerp(c0.R, c1.R, t), Lerp(c0.G, c1.G, t),
|
||||||
|
Lerp(c0.B, c1.B, t), Lerp(c0.A, c1.A, t));
|
||||||
|
int idx = doc.EnsureColorCached(c);
|
||||||
|
if (horizontal)
|
||||||
|
for (int y = 0; y < h; y++) pixels[y * w + s] = idx;
|
||||||
|
else
|
||||||
|
for (int x = 0; x < w; x++) pixels[s * w + x] = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillStripes(int[] pixels, int w, int h, int[] ci, int stripe, bool horizontal)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < h; y++)
|
||||||
|
for (int x = 0; x < w; x++)
|
||||||
|
{
|
||||||
|
int coord = horizontal ? y : x;
|
||||||
|
pixels[y * w + x] = ci[(coord / stripe) % ci.Length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillCircles(int[] pixels, int w, int h, int[] ci, int ringWidth)
|
||||||
|
{
|
||||||
|
double cx = (w - 1) / 2.0, cy = (h - 1) / 2.0;
|
||||||
|
for (int y = 0; y < h; y++)
|
||||||
|
for (int x = 0; x < w; x++)
|
||||||
|
{
|
||||||
|
double dist = Math.Sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy));
|
||||||
|
int ring = (int)(dist / ringWidth);
|
||||||
|
pixels[y * w + x] = ci[ring % ci.Length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillTile(int[] pixels, int w, int h, int[] ci, int tileW, int tileH)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < h; y++)
|
||||||
|
for (int x = 0; x < w; x++)
|
||||||
|
{
|
||||||
|
int tx = (x / tileW) % ci.Length;
|
||||||
|
int ty = (y / tileH) % ci.Length;
|
||||||
|
pixels[y * w + x] = ci[(tx + ty) % ci.Length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte Lerp(byte a, byte b, double t)
|
||||||
|
=> (byte)Math.Clamp((int)(a + (b - a) * t + 0.5), 0, 255);
|
||||||
|
}
|
||||||
@@ -56,7 +56,10 @@ public class PixelCanvas : Control
|
|||||||
private Point _panStart;
|
private Point _panStart;
|
||||||
private double _panStartOffsetX, _panStartOffsetY;
|
private double _panStartOffsetX, _panStartOffsetY;
|
||||||
private bool _viewportInitialized;
|
private bool _viewportInitialized;
|
||||||
|
private int _lastBitmapWidth;
|
||||||
|
private int _lastBitmapHeight;
|
||||||
private (int X, int Y)? _lastCursorPixel;
|
private (int X, int Y)? _lastCursorPixel;
|
||||||
|
private Point? _lastScreenPos;
|
||||||
|
|
||||||
private ScrollBar? _hScrollBar;
|
private ScrollBar? _hScrollBar;
|
||||||
private ScrollBar? _vScrollBar;
|
private ScrollBar? _vScrollBar;
|
||||||
@@ -248,6 +251,7 @@ public class PixelCanvas : Control
|
|||||||
if (_suppressScrollSync) return;
|
if (_suppressScrollSync) return;
|
||||||
var (imgW, imgH) = GetImageSize();
|
var (imgW, imgH) = GetImageSize();
|
||||||
_viewport.SetOffset(-e.NewValue, _viewport.OffsetY, imgW, imgH, Bounds.Width, Bounds.Height);
|
_viewport.SetOffset(-e.NewValue, _viewport.OffsetY, imgW, imgH, Bounds.Width, Bounds.Height);
|
||||||
|
RecalcCursorPixel();
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,6 +260,7 @@ public class PixelCanvas : Control
|
|||||||
if (_suppressScrollSync) return;
|
if (_suppressScrollSync) return;
|
||||||
var (imgW, imgH) = GetImageSize();
|
var (imgW, imgH) = GetImageSize();
|
||||||
_viewport.SetOffset(_viewport.OffsetX, -e.NewValue, imgW, imgH, Bounds.Width, Bounds.Height);
|
_viewport.SetOffset(_viewport.OffsetX, -e.NewValue, imgW, imgH, Bounds.Width, Bounds.Height);
|
||||||
|
RecalcCursorPixel();
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +284,20 @@ public class PixelCanvas : Control
|
|||||||
return (px, py);
|
return (px, py);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recalculates the pixel coordinate under the cursor after a viewport change.
|
||||||
|
/// </summary>
|
||||||
|
private void RecalcCursorPixel()
|
||||||
|
{
|
||||||
|
if (_lastScreenPos is null) return;
|
||||||
|
var pixel = ScreenToPixelClamped(_lastScreenPos.Value);
|
||||||
|
if (pixel != _lastCursorPixel)
|
||||||
|
{
|
||||||
|
_lastCursorPixel = pixel;
|
||||||
|
CursorPixelChanged?.Invoke(pixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnPointerWheelChanged(e);
|
base.OnPointerWheelChanged(e);
|
||||||
@@ -305,6 +324,7 @@ public class PixelCanvas : Control
|
|||||||
_viewport.Pan(dx, dy, imgW, imgH, Bounds.Width, Bounds.Height);
|
_viewport.Pan(dx, dy, imgW, imgH, Bounds.Width, Bounds.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RecalcCursorPixel();
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
@@ -338,6 +358,7 @@ public class PixelCanvas : Control
|
|||||||
{
|
{
|
||||||
base.OnPointerMoved(e);
|
base.OnPointerMoved(e);
|
||||||
var pos = e.GetPosition(this);
|
var pos = e.GetPosition(this);
|
||||||
|
_lastScreenPos = pos;
|
||||||
|
|
||||||
if (_isPanning)
|
if (_isPanning)
|
||||||
{
|
{
|
||||||
@@ -346,12 +367,12 @@ public class PixelCanvas : Control
|
|||||||
_panStartOffsetX + (pos.X - _panStart.X),
|
_panStartOffsetX + (pos.X - _panStart.X),
|
||||||
_panStartOffsetY + (pos.Y - _panStart.Y),
|
_panStartOffsetY + (pos.Y - _panStart.Y),
|
||||||
imgW, imgH, Bounds.Width, Bounds.Height);
|
imgW, imgH, Bounds.Width, Bounds.Height);
|
||||||
|
RecalcCursorPixel();
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update preview cursor position
|
|
||||||
var pixel = ScreenToPixelClamped(pos);
|
var pixel = ScreenToPixelClamped(pos);
|
||||||
if (pixel != _lastCursorPixel)
|
if (pixel != _lastCursorPixel)
|
||||||
{
|
{
|
||||||
@@ -386,6 +407,7 @@ public class PixelCanvas : Control
|
|||||||
protected override void OnPointerExited(PointerEventArgs e)
|
protected override void OnPointerExited(PointerEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnPointerExited(e);
|
base.OnPointerExited(e);
|
||||||
|
_lastScreenPos = null;
|
||||||
if (_lastCursorPixel is not null)
|
if (_lastCursorPixel is not null)
|
||||||
{
|
{
|
||||||
_lastCursorPixel = null;
|
_lastCursorPixel = null;
|
||||||
@@ -400,6 +422,16 @@ public class PixelCanvas : Control
|
|||||||
{
|
{
|
||||||
base.OnPropertyChanged(change);
|
base.OnPropertyChanged(change);
|
||||||
if (change.Property == SourceBitmapProperty)
|
if (change.Property == SourceBitmapProperty)
|
||||||
_viewportInitialized = false;
|
{
|
||||||
|
var bmp = change.GetNewValue<WriteableBitmap?>();
|
||||||
|
int w = bmp?.PixelSize.Width ?? 0;
|
||||||
|
int h = bmp?.PixelSize.Height ?? 0;
|
||||||
|
if (w != _lastBitmapWidth || h != _lastBitmapHeight)
|
||||||
|
{
|
||||||
|
_lastBitmapWidth = w;
|
||||||
|
_lastBitmapHeight = h;
|
||||||
|
_viewportInitialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ sealed class Program
|
|||||||
=> AppBuilder.Configure<App>()
|
=> AppBuilder.Configure<App>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.WithInterFont()
|
.WithInterFont()
|
||||||
.LogToTrace();
|
.LogToTrace()
|
||||||
|
.With(new X11PlatformOptions { OverlayPopups = true });
|
||||||
|
|
||||||
// TODO: temporary tests — remove after verification stages.
|
// TODO: temporary tests — remove after verification stages.
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ public partial class EditorViewModel : ViewModelBase
|
|||||||
SyncLayersAndCanvas(doc);
|
SyncLayersAndCanvas(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Re-syncs the Documents observable collection after an external modification
|
||||||
|
/// to Container.Documents (e.g. pattern generation adding a document).
|
||||||
|
/// </summary>
|
||||||
|
public void SyncAfterExternalChange() => SyncDocumentsList();
|
||||||
|
|
||||||
private void SyncDocumentsList()
|
private void SyncDocumentsList()
|
||||||
{
|
{
|
||||||
Documents.Clear();
|
Documents.Clear();
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Minint.Core.Models;
|
||||||
|
using Minint.Core.Services;
|
||||||
|
using Minint.Core.Services.Impl;
|
||||||
using Minint.Infrastructure.Serialization;
|
using Minint.Infrastructure.Serialization;
|
||||||
|
using Minint.Views;
|
||||||
|
|
||||||
namespace Minint.ViewModels;
|
namespace Minint.ViewModels;
|
||||||
|
|
||||||
public partial class MainWindowViewModel : ViewModelBase
|
public partial class MainWindowViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly MinintSerializer _serializer = new();
|
private readonly MinintSerializer _serializer = new();
|
||||||
|
private readonly IImageEffectsService _effects = new ImageEffectsService();
|
||||||
|
private readonly IPatternGenerator _patternGen = new PatternGenerator();
|
||||||
|
|
||||||
private static readonly FilePickerFileType MinintFileType = new("Minint Files")
|
private static readonly FilePickerFileType MinintFileType = new("Minint Files")
|
||||||
{
|
{
|
||||||
@@ -25,11 +31,10 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _statusText = "Ready";
|
private string _statusText = "Ready";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set by the view so that file dialogs can use the correct parent window.
|
|
||||||
/// </summary>
|
|
||||||
public TopLevel? Owner { get; set; }
|
public TopLevel? Owner { get; set; }
|
||||||
|
|
||||||
|
#region File commands
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void NewFile()
|
private void NewFile()
|
||||||
{
|
{
|
||||||
@@ -72,13 +77,9 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
if (Editor.Container is null) return;
|
if (Editor.Container is null) return;
|
||||||
|
|
||||||
if (Editor.FilePath is not null)
|
if (Editor.FilePath is not null)
|
||||||
{
|
|
||||||
await SaveToPathAsync(Editor.FilePath);
|
await SaveToPathAsync(Editor.FilePath);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
await SaveFileAsAsync();
|
await SaveFileAsAsync();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -121,4 +122,111 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
StatusText = $"Error saving file: {ex.Message}";
|
StatusText = $"Error saving file: {ex.Message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Effects (A1, A2)
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ApplyContrastAsync()
|
||||||
|
{
|
||||||
|
if (Editor.ActiveDocument is null || Owner is not Window window) return;
|
||||||
|
|
||||||
|
var dialog = new ContrastDialog();
|
||||||
|
var result = await dialog.ShowDialog<bool?>(window);
|
||||||
|
if (result != true) return;
|
||||||
|
|
||||||
|
_effects.ApplyContrast(Editor.ActiveDocument, dialog.Factor);
|
||||||
|
Editor.RefreshCanvas();
|
||||||
|
StatusText = $"Contrast ×{dialog.Factor:F1} applied.";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void ApplyGrayscale()
|
||||||
|
{
|
||||||
|
if (Editor.ActiveDocument is null) return;
|
||||||
|
|
||||||
|
_effects.ApplyGrayscale(Editor.ActiveDocument);
|
||||||
|
Editor.RefreshCanvas();
|
||||||
|
StatusText = "Grayscale applied.";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Copy fragment (A4)
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task CopyFragmentAsync()
|
||||||
|
{
|
||||||
|
if (Editor.Container is null || Owner is not Window window) return;
|
||||||
|
|
||||||
|
var docs = Editor.Container.Documents;
|
||||||
|
if (docs.Count == 0) return;
|
||||||
|
|
||||||
|
var dialog = new CopyFragmentDialog(docs, Editor.Container.Width, Editor.Container.Height);
|
||||||
|
var result = await dialog.ShowDialog<bool?>(window);
|
||||||
|
if (result != true) return;
|
||||||
|
|
||||||
|
if (dialog.SourceDocument is null || dialog.DestDocument is null)
|
||||||
|
{
|
||||||
|
StatusText = "Copy failed: no document selected.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dialog.SourceLayerIndex < 0 || dialog.DestLayerIndex < 0)
|
||||||
|
{
|
||||||
|
StatusText = "Copy failed: no layer selected.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Editor.CopyFragment(
|
||||||
|
dialog.SourceDocument, dialog.SourceLayerIndex,
|
||||||
|
dialog.SourceX, dialog.SourceY,
|
||||||
|
dialog.FragmentWidth, dialog.FragmentHeight,
|
||||||
|
dialog.DestDocument, dialog.DestLayerIndex,
|
||||||
|
dialog.DestX, dialog.DestY);
|
||||||
|
StatusText = $"Copied {dialog.FragmentWidth}×{dialog.FragmentHeight} fragment.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusText = $"Copy failed: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Pattern generation (Б4)
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task GeneratePatternAsync()
|
||||||
|
{
|
||||||
|
if (Editor.Container is null || Owner is not Window window) return;
|
||||||
|
|
||||||
|
var dialog = new PatternDialog();
|
||||||
|
var result = await dialog.ShowDialog<bool?>(window);
|
||||||
|
if (result != true) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var doc = _patternGen.Generate(
|
||||||
|
dialog.SelectedPattern,
|
||||||
|
Editor.Container.Width,
|
||||||
|
Editor.Container.Height,
|
||||||
|
[dialog.PatternColor1, dialog.PatternColor2],
|
||||||
|
dialog.PatternParam1,
|
||||||
|
dialog.PatternParam2);
|
||||||
|
|
||||||
|
Editor.Container.Documents.Add(doc);
|
||||||
|
Editor.SyncAfterExternalChange();
|
||||||
|
Editor.SelectDocument(doc);
|
||||||
|
StatusText = $"Pattern '{dialog.SelectedPattern}' generated.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusText = $"Pattern generation failed: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
20
Minint/Views/ContrastDialog.axaml
Normal file
20
Minint/Views/ContrastDialog.axaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="Minint.Views.ContrastDialog"
|
||||||
|
Title="Adjust Contrast"
|
||||||
|
Width="320"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
CanResize="False"
|
||||||
|
SizeToContent="Height">
|
||||||
|
<StackPanel Margin="16" Spacing="10">
|
||||||
|
<TextBlock Text="Contrast factor (0 = gray, 1 = no change, >1 = more contrast):"/>
|
||||||
|
<Slider x:Name="FactorSlider" Minimum="0" Maximum="3" Value="1"
|
||||||
|
TickFrequency="0.1" IsSnapToTickEnabled="True"/>
|
||||||
|
<TextBlock x:Name="FactorLabel" Text="1.0" HorizontalAlignment="Center"/>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,8,0,0">
|
||||||
|
<Button Content="Apply" x:Name="OkButton" IsDefault="True" Padding="16,6"/>
|
||||||
|
<Button Content="Cancel" x:Name="CancelButton" IsCancel="True" Padding="16,6"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
24
Minint/Views/ContrastDialog.axaml.cs
Normal file
24
Minint/Views/ContrastDialog.axaml.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace Minint.Views;
|
||||||
|
|
||||||
|
public partial class ContrastDialog : Window
|
||||||
|
{
|
||||||
|
public double Factor => FactorSlider.Value;
|
||||||
|
|
||||||
|
public ContrastDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
FactorSlider.PropertyChanged += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.Property == Slider.ValueProperty)
|
||||||
|
FactorLabel.Text = FactorSlider.Value.ToString("F1");
|
||||||
|
};
|
||||||
|
|
||||||
|
OkButton.Click += (_, _) => Close(true);
|
||||||
|
CancelButton.Click += (_, _) => Close(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Minint/Views/CopyFragmentDialog.axaml
Normal file
51
Minint/Views/CopyFragmentDialog.axaml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="Minint.Views.CopyFragmentDialog"
|
||||||
|
Title="Copy Fragment"
|
||||||
|
Width="380" Height="420"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
CanResize="False"
|
||||||
|
SizeToContent="Height">
|
||||||
|
<StackPanel Margin="16" Spacing="8">
|
||||||
|
<TextBlock Text="Source" FontWeight="SemiBold"/>
|
||||||
|
<Grid ColumnDefinitions="100,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto" RowSpacing="4" ColumnSpacing="8">
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0" Text="Document:" VerticalAlignment="Center"/>
|
||||||
|
<ComboBox Grid.Row="0" Grid.Column="1" x:Name="SrcDocCombo"
|
||||||
|
HorizontalAlignment="Stretch"/>
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="Layer:" VerticalAlignment="Center"/>
|
||||||
|
<ComboBox Grid.Row="1" Grid.Column="1" x:Name="SrcLayerCombo"
|
||||||
|
HorizontalAlignment="Stretch"/>
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="0" Text="X:" VerticalAlignment="Center"/>
|
||||||
|
<NumericUpDown Grid.Row="2" Grid.Column="1" x:Name="SrcX" Value="0" Minimum="0" FormatString="0"/>
|
||||||
|
<TextBlock Grid.Row="3" Grid.Column="0" Text="Y:" VerticalAlignment="Center"/>
|
||||||
|
<NumericUpDown Grid.Row="3" Grid.Column="1" x:Name="SrcY" Value="0" Minimum="0" FormatString="0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="100,*" RowDefinitions="Auto,Auto" RowSpacing="4" ColumnSpacing="8" Margin="0,4,0,0">
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0" Text="Width:" VerticalAlignment="Center"/>
|
||||||
|
<NumericUpDown Grid.Row="0" Grid.Column="1" x:Name="RegionW" Value="16" Minimum="1" FormatString="0"/>
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="Height:" VerticalAlignment="Center"/>
|
||||||
|
<NumericUpDown Grid.Row="1" Grid.Column="1" x:Name="RegionH" Value="16" Minimum="1" FormatString="0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Separator Margin="0,8"/>
|
||||||
|
<TextBlock Text="Destination" FontWeight="SemiBold"/>
|
||||||
|
<Grid ColumnDefinitions="100,*" RowDefinitions="Auto,Auto,Auto,Auto" RowSpacing="4" ColumnSpacing="8">
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0" Text="Document:" VerticalAlignment="Center"/>
|
||||||
|
<ComboBox Grid.Row="0" Grid.Column="1" x:Name="DstDocCombo"
|
||||||
|
HorizontalAlignment="Stretch"/>
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="Layer:" VerticalAlignment="Center"/>
|
||||||
|
<ComboBox Grid.Row="1" Grid.Column="1" x:Name="DstLayerCombo"
|
||||||
|
HorizontalAlignment="Stretch"/>
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="0" Text="X:" VerticalAlignment="Center"/>
|
||||||
|
<NumericUpDown Grid.Row="2" Grid.Column="1" x:Name="DstX" Value="0" Minimum="0" FormatString="0"/>
|
||||||
|
<TextBlock Grid.Row="3" Grid.Column="0" Text="Y:" VerticalAlignment="Center"/>
|
||||||
|
<NumericUpDown Grid.Row="3" Grid.Column="1" x:Name="DstY" Value="0" Minimum="0" FormatString="0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,12,0,0">
|
||||||
|
<Button Content="Copy" x:Name="OkButton" IsDefault="True" Padding="16,6"/>
|
||||||
|
<Button Content="Cancel" x:Name="CancelButton" IsCancel="True" Padding="16,6"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
91
Minint/Views/CopyFragmentDialog.axaml.cs
Normal file
91
Minint/Views/CopyFragmentDialog.axaml.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Minint.Core.Models;
|
||||||
|
|
||||||
|
namespace Minint.Views;
|
||||||
|
|
||||||
|
public partial class CopyFragmentDialog : Window
|
||||||
|
{
|
||||||
|
private readonly List<MinintDocument> _documents;
|
||||||
|
|
||||||
|
public MinintDocument? SourceDocument => SrcDocCombo.SelectedItem as MinintDocument;
|
||||||
|
public int SourceLayerIndex => SrcLayerCombo.SelectedIndex;
|
||||||
|
public int SourceX => (int)(SrcX.Value ?? 0);
|
||||||
|
public int SourceY => (int)(SrcY.Value ?? 0);
|
||||||
|
public int FragmentWidth => (int)(RegionW.Value ?? 1);
|
||||||
|
public int FragmentHeight => (int)(RegionH.Value ?? 1);
|
||||||
|
public MinintDocument? DestDocument => DstDocCombo.SelectedItem as MinintDocument;
|
||||||
|
public int DestLayerIndex => DstLayerCombo.SelectedIndex;
|
||||||
|
public int DestX => (int)(DstX.Value ?? 0);
|
||||||
|
public int DestY => (int)(DstY.Value ?? 0);
|
||||||
|
|
||||||
|
public CopyFragmentDialog()
|
||||||
|
{
|
||||||
|
_documents = [];
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CopyFragmentDialog(List<MinintDocument> documents, int maxW, int maxH) : this()
|
||||||
|
{
|
||||||
|
_documents = documents;
|
||||||
|
|
||||||
|
SrcDocCombo.ItemsSource = documents;
|
||||||
|
SrcDocCombo.DisplayMemberBinding = new Avalonia.Data.Binding("Name");
|
||||||
|
DstDocCombo.ItemsSource = documents;
|
||||||
|
DstDocCombo.DisplayMemberBinding = new Avalonia.Data.Binding("Name");
|
||||||
|
|
||||||
|
SrcX.Maximum = maxW - 1;
|
||||||
|
SrcY.Maximum = maxH - 1;
|
||||||
|
DstX.Maximum = maxW - 1;
|
||||||
|
DstY.Maximum = maxH - 1;
|
||||||
|
RegionW.Maximum = maxW;
|
||||||
|
RegionH.Maximum = maxH;
|
||||||
|
|
||||||
|
if (documents.Count > 0)
|
||||||
|
{
|
||||||
|
SrcDocCombo.SelectedIndex = 0;
|
||||||
|
DstDocCombo.SelectedIndex = documents.Count > 1 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SrcDocCombo.SelectionChanged += (_, _) => UpdateSrcLayers();
|
||||||
|
DstDocCombo.SelectionChanged += (_, _) => UpdateDstLayers();
|
||||||
|
|
||||||
|
UpdateSrcLayers();
|
||||||
|
UpdateDstLayers();
|
||||||
|
|
||||||
|
OkButton.Click += OnOkClick;
|
||||||
|
CancelButton.Click += OnCancelClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSrcLayers()
|
||||||
|
{
|
||||||
|
if (SrcDocCombo.SelectedItem is MinintDocument doc)
|
||||||
|
{
|
||||||
|
SrcLayerCombo.ItemsSource = doc.Layers;
|
||||||
|
SrcLayerCombo.DisplayMemberBinding = new Avalonia.Data.Binding("Name");
|
||||||
|
if (doc.Layers.Count > 0) SrcLayerCombo.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDstLayers()
|
||||||
|
{
|
||||||
|
if (DstDocCombo.SelectedItem is MinintDocument doc)
|
||||||
|
{
|
||||||
|
DstLayerCombo.ItemsSource = doc.Layers;
|
||||||
|
DstLayerCombo.DisplayMemberBinding = new Avalonia.Data.Binding("Name");
|
||||||
|
if (doc.Layers.Count > 0) DstLayerCombo.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOkClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCancelClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,8 @@
|
|||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
Icon="/Assets/avalonia-logo.ico"
|
Icon="/Assets/avalonia-logo.ico"
|
||||||
Title="{Binding Editor.Title}"
|
Title="{Binding Editor.Title}"
|
||||||
Width="1024" Height="700">
|
Width="1024" Height="700"
|
||||||
|
ToolTip.ShowDelay="400">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<vm:MainWindowViewModel/>
|
<vm:MainWindowViewModel/>
|
||||||
@@ -25,6 +26,19 @@
|
|||||||
<MenuItem Header="_Save" Command="{Binding SaveFileCommand}" HotKey="Ctrl+S"/>
|
<MenuItem Header="_Save" Command="{Binding SaveFileCommand}" HotKey="Ctrl+S"/>
|
||||||
<MenuItem Header="Save _As…" Command="{Binding SaveFileAsCommand}" HotKey="Ctrl+Shift+S"/>
|
<MenuItem Header="Save _As…" Command="{Binding SaveFileAsCommand}" HotKey="Ctrl+Shift+S"/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem Header="_Edit">
|
||||||
|
<MenuItem Header="Copy _Fragment…" Command="{Binding CopyFragmentCommand}"
|
||||||
|
ToolTip.Tip="Copy a rectangular region between documents"/>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="_Image">
|
||||||
|
<MenuItem Header="Adjust _Contrast…" Command="{Binding ApplyContrastCommand}"
|
||||||
|
ToolTip.Tip="Adjust contrast of the active document's palette"/>
|
||||||
|
<MenuItem Header="Convert to _Grayscale" Command="{Binding ApplyGrayscaleCommand}"
|
||||||
|
ToolTip.Tip="Convert active document to grayscale"/>
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="Generate _Pattern…" Command="{Binding GeneratePatternCommand}"
|
||||||
|
ToolTip.Tip="Generate a new document with a parametric pattern"/>
|
||||||
|
</MenuItem>
|
||||||
<MenuItem Header="_View">
|
<MenuItem Header="_View">
|
||||||
<MenuItem Header="Pixel _Grid" ToggleType="CheckBox"
|
<MenuItem Header="Pixel _Grid" ToggleType="CheckBox"
|
||||||
IsChecked="{Binding Editor.ShowGrid}" HotKey="Ctrl+G"/>
|
IsChecked="{Binding Editor.ShowGrid}" HotKey="Ctrl+G"/>
|
||||||
@@ -83,13 +97,13 @@
|
|||||||
<DockPanel>
|
<DockPanel>
|
||||||
<TextBlock DockPanel.Dock="Top" Text="Documents" FontWeight="SemiBold" Margin="0,0,0,4"/>
|
<TextBlock DockPanel.Dock="Top" Text="Documents" FontWeight="SemiBold" Margin="0,0,0,4"/>
|
||||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="2" Margin="0,4,0,0">
|
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="2" Margin="0,4,0,0">
|
||||||
<Button Content="+" ToolTip.Tip="Add document"
|
<Button Content="+" ToolTip.Tip="Add a new document (frame)"
|
||||||
Command="{Binding Editor.AddDocumentCommand}" Padding="6,2"/>
|
Command="{Binding Editor.AddDocumentCommand}" Padding="6,2"/>
|
||||||
<Button Content="−" ToolTip.Tip="Remove document"
|
<Button Content="−" ToolTip.Tip="Remove selected document"
|
||||||
Command="{Binding Editor.RemoveDocumentCommand}" Padding="6,2"/>
|
Command="{Binding Editor.RemoveDocumentCommand}" Padding="6,2"/>
|
||||||
<Button Content="▲" ToolTip.Tip="Move up"
|
<Button Content="▲" ToolTip.Tip="Move document up in the list"
|
||||||
Command="{Binding Editor.MoveDocumentUpCommand}" Padding="6,2"/>
|
Command="{Binding Editor.MoveDocumentUpCommand}" Padding="6,2"/>
|
||||||
<Button Content="▼" ToolTip.Tip="Move down"
|
<Button Content="▼" ToolTip.Tip="Move document down in the list"
|
||||||
Command="{Binding Editor.MoveDocumentDownCommand}" Padding="6,2"/>
|
Command="{Binding Editor.MoveDocumentDownCommand}" Padding="6,2"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ListBox ItemsSource="{Binding Editor.Documents}"
|
<ListBox ItemsSource="{Binding Editor.Documents}"
|
||||||
@@ -123,15 +137,15 @@
|
|||||||
<DockPanel>
|
<DockPanel>
|
||||||
<TextBlock DockPanel.Dock="Top" Text="Layers" FontWeight="SemiBold" Margin="0,0,0,4"/>
|
<TextBlock DockPanel.Dock="Top" Text="Layers" FontWeight="SemiBold" Margin="0,0,0,4"/>
|
||||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="2" Margin="0,4,0,0">
|
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="2" Margin="0,4,0,0">
|
||||||
<Button Content="+" ToolTip.Tip="Add layer"
|
<Button Content="+" ToolTip.Tip="Add a new empty layer"
|
||||||
Command="{Binding Editor.AddLayerCommand}" Padding="6,2"/>
|
Command="{Binding Editor.AddLayerCommand}" Padding="6,2"/>
|
||||||
<Button Content="−" ToolTip.Tip="Remove layer"
|
<Button Content="−" ToolTip.Tip="Remove selected layer"
|
||||||
Command="{Binding Editor.RemoveLayerCommand}" Padding="6,2"/>
|
Command="{Binding Editor.RemoveLayerCommand}" Padding="6,2"/>
|
||||||
<Button Content="▲" ToolTip.Tip="Move up"
|
<Button Content="▲" ToolTip.Tip="Move layer up (draw later, appears on top)"
|
||||||
Command="{Binding Editor.MoveLayerUpCommand}" Padding="6,2"/>
|
Command="{Binding Editor.MoveLayerUpCommand}" Padding="6,2"/>
|
||||||
<Button Content="▼" ToolTip.Tip="Move down"
|
<Button Content="▼" ToolTip.Tip="Move layer down (draw earlier, appears below)"
|
||||||
Command="{Binding Editor.MoveLayerDownCommand}" Padding="6,2"/>
|
Command="{Binding Editor.MoveLayerDownCommand}" Padding="6,2"/>
|
||||||
<Button Content="⧉" ToolTip.Tip="Duplicate layer"
|
<Button Content="⧉" ToolTip.Tip="Duplicate selected layer with all pixels"
|
||||||
Command="{Binding Editor.DuplicateLayerCommand}" Padding="6,2"/>
|
Command="{Binding Editor.DuplicateLayerCommand}" Padding="6,2"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ListBox ItemsSource="{Binding Editor.Layers}"
|
<ListBox ItemsSource="{Binding Editor.Layers}"
|
||||||
|
|||||||
36
Minint/Views/PatternDialog.axaml
Normal file
36
Minint/Views/PatternDialog.axaml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="Minint.Views.PatternDialog"
|
||||||
|
Title="Generate Pattern"
|
||||||
|
Width="360"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
CanResize="False"
|
||||||
|
SizeToContent="Height">
|
||||||
|
<StackPanel Margin="16" Spacing="8">
|
||||||
|
<Grid ColumnDefinitions="100,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" RowSpacing="6" ColumnSpacing="8">
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0" Text="Pattern:" VerticalAlignment="Center"/>
|
||||||
|
<ComboBox Grid.Row="0" Grid.Column="1" x:Name="PatternCombo" HorizontalAlignment="Stretch"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="Color 1:" VerticalAlignment="Center"/>
|
||||||
|
<ColorPicker Grid.Row="1" Grid.Column="1" x:Name="Color1Picker" IsAlphaVisible="False"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="0" Text="Color 2:" VerticalAlignment="Center"/>
|
||||||
|
<ColorPicker Grid.Row="2" Grid.Column="1" x:Name="Color2Picker" IsAlphaVisible="False"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="3" Grid.Column="0" Text="Param 1:" VerticalAlignment="Center"/>
|
||||||
|
<NumericUpDown Grid.Row="3" Grid.Column="1" x:Name="Param1"
|
||||||
|
Value="8" Minimum="1" Maximum="256" FormatString="0"
|
||||||
|
ToolTip.Tip="Cell/stripe/ring size"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="4" Grid.Column="0" Text="Param 2:" VerticalAlignment="Center"/>
|
||||||
|
<NumericUpDown Grid.Row="4" Grid.Column="1" x:Name="Param2"
|
||||||
|
Value="8" Minimum="1" Maximum="256" FormatString="0"
|
||||||
|
ToolTip.Tip="Tile height (for Tile pattern)"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8" Margin="0,12,0,0">
|
||||||
|
<Button Content="Generate" x:Name="OkButton" IsDefault="True" Padding="16,6"/>
|
||||||
|
<Button Content="Cancel" x:Name="CancelButton" IsCancel="True" Padding="16,6"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
50
Minint/Views/PatternDialog.axaml.cs
Normal file
50
Minint/Views/PatternDialog.axaml.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Minint.Core.Models;
|
||||||
|
using Minint.Core.Services;
|
||||||
|
|
||||||
|
namespace Minint.Views;
|
||||||
|
|
||||||
|
public partial class PatternDialog : Window
|
||||||
|
{
|
||||||
|
public PatternType SelectedPattern =>
|
||||||
|
PatternCombo.SelectedItem is PatternType pt ? pt : PatternType.Checkerboard;
|
||||||
|
|
||||||
|
public RgbaColor PatternColor1
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var c = Color1Picker.Color;
|
||||||
|
return new RgbaColor(c.R, c.G, c.B, c.A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RgbaColor PatternColor2
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var c = Color2Picker.Color;
|
||||||
|
return new RgbaColor(c.R, c.G, c.B, c.A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int PatternParam1 => (int)(Param1.Value ?? 8);
|
||||||
|
public int PatternParam2 => (int)(Param2.Value ?? 8);
|
||||||
|
|
||||||
|
public PatternDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
PatternCombo.ItemsSource = Enum.GetValues<PatternType>().ToList();
|
||||||
|
PatternCombo.SelectedIndex = 0;
|
||||||
|
|
||||||
|
Color1Picker.Color = Color.FromRgb(0, 0, 0);
|
||||||
|
Color2Picker.Color = Color.FromRgb(255, 255, 255);
|
||||||
|
|
||||||
|
OkButton.Click += (_, _) => Close(true);
|
||||||
|
CancelButton.Click += (_, _) => Close(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user