Этап 10

This commit is contained in:
2026-03-29 18:19:39 +03:00
parent 415b1a41fc
commit 770dd629f5
5 changed files with 655 additions and 1 deletions

View File

@@ -5,6 +5,7 @@ using System.ComponentModel;
using System.Linq;
using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Minint.Core.Models;
@@ -341,6 +342,7 @@ public partial class EditorViewModel : ViewModelBase
public void OnToolDown(int px, int py)
{
if (IsPlaying) return;
if (Container is null || ActiveDocument is null || ActiveLayer is null)
return;
@@ -374,6 +376,7 @@ public partial class EditorViewModel : ViewModelBase
public void OnToolDrag(int px, int py)
{
if (IsPlaying) return;
if (ActiveTool is ToolType.Fill or ToolType.Select) return;
OnToolDown(px, py);
}
@@ -408,6 +411,7 @@ public partial class EditorViewModel : ViewModelBase
/// <summary>Called by PixelCanvas when selection drag starts.</summary>
public void BeginSelection(int px, int py)
{
if (IsPlaying) return;
SelectionRect = (px, py, 0, 0);
}
@@ -490,13 +494,14 @@ public partial class EditorViewModel : ViewModelBase
public void MovePaste(int px, int py)
{
if (!IsPasting) return;
if (IsPlaying || !IsPasting) return;
PastePosition = (px, py);
}
[RelayCommand]
public void CommitPaste()
{
if (IsPlaying) return;
if (!IsPasting || Clipboard is null || ActiveDocument is null || ActiveLayer is null || Container is null)
return;
@@ -603,6 +608,125 @@ public partial class EditorViewModel : ViewModelBase
#endregion
#region Animation playback
private DispatcherTimer? _animationTimer;
private int _animationFrameIndex;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsNotPlaying))]
private bool _isPlaying;
public bool IsNotPlaying => !IsPlaying;
[RelayCommand]
private void PlayAnimation()
{
if (Container is null || Container.Documents.Count < 2) return;
if (IsPlaying) return;
IsPlaying = true;
_animationFrameIndex = ActiveDocument is not null
? Container.Documents.IndexOf(ActiveDocument)
: 0;
if (_animationFrameIndex < 0) _animationFrameIndex = 0;
AdvanceAnimationFrame();
}
[RelayCommand]
private void StopAnimation()
{
_animationTimer?.Stop();
_animationTimer = null;
IsPlaying = false;
}
private void AdvanceAnimationFrame()
{
if (Container is null || !IsPlaying)
{
StopAnimation();
return;
}
var docs = Container.Documents;
if (docs.Count == 0)
{
StopAnimation();
return;
}
_animationFrameIndex %= docs.Count;
var doc = docs[_animationFrameIndex];
_suppressDocumentSync = true;
ActiveDocument = doc;
_suppressDocumentSync = false;
RefreshCanvasFor(doc);
uint delay = doc.FrameDelayMs;
if (delay < 10) delay = 10;
_animationTimer?.Stop();
_animationTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(delay) };
_animationTimer.Tick += (_, _) =>
{
_animationTimer?.Stop();
_animationFrameIndex++;
AdvanceAnimationFrame();
};
_animationTimer.Start();
}
private void RefreshCanvasFor(MinintDocument doc)
{
if (Container is null)
{
CanvasBitmap = null;
return;
}
int w = Container.Width;
int h = Container.Height;
uint[] argb = _compositor.Composite(doc, w, h);
var bmp = new WriteableBitmap(
new PixelSize(w, h),
new Vector(96, 96),
Avalonia.Platform.PixelFormat.Bgra8888);
using (var fb = bmp.Lock())
{
unsafe
{
var dst = new Span<uint>((void*)fb.Address, w * h);
for (int i = 0; i < argb.Length; i++)
{
uint px = argb[i];
byte a2 = (byte)(px >> 24);
byte r2 = (byte)((px >> 16) & 0xFF);
byte g2 = (byte)((px >> 8) & 0xFF);
byte b2 = (byte)(px & 0xFF);
if (a2 == 255) { dst[i] = px; }
else if (a2 == 0) { dst[i] = 0; }
else
{
r2 = (byte)(r2 * a2 / 255);
g2 = (byte)(g2 * a2 / 255);
b2 = (byte)(b2 * a2 / 255);
dst[i] = (uint)(b2 | (g2 << 8) | (r2 << 16) | (a2 << 24));
}
}
}
}
CanvasBitmap = bmp;
}
#endregion
public ICompositor Compositor => _compositor;
public IPaletteService PaletteService => _paletteService;
public IDrawingService DrawingService => _drawingService;