Skip to content

Commit

Permalink
fix(Studio): Content being drawn while document is being updated
Browse files Browse the repository at this point in the history
  • Loading branch information
psyGamer committed Dec 27, 2024
1 parent aeb3610 commit f504d27
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 122 deletions.
53 changes: 30 additions & 23 deletions Studio/CelesteStudio.GTK/SkiaDrawableHandler.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using Cairo;
using CelesteStudio.Controls;
using CelesteStudio.Util;
using Eto.Forms;
using Eto.GtkSharp.Forms;
using SkiaSharp;
using System;

namespace CelesteStudio.GTK;

public class SkiaDrawableHandler : GtkPanel<Gtk.EventBox, SkiaDrawable, SkiaDrawable.ICallback>, SkiaDrawable.IHandler {
public class SkiaDrawableHandler : GtkPanel<Gtk.EventBox, SkiaDrawable, Control.ICallback>, SkiaDrawable.IHandler {
private Gtk.Box content = null!;

protected override WeakConnector CreateConnector() => new SkiaDrawableConnector();
Expand Down Expand Up @@ -55,31 +56,37 @@ public void HandleDrawn(object o, Gtk.DrawnArgs args) {
}

var drawable = Handler.Widget;
int width = drawable.DrawWidth, height = drawable.DrawHeight;
if (surface == null || imageSurface == null || width != bitmap?.Width || height != bitmap?.Height) {
var colorType = SKImageInfo.PlatformColorType;

bitmap?.Dispose();
bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul);
IntPtr pixels = bitmap.GetPixels();

surface?.Dispose();
surface = SKSurface.Create(new SKImageInfo(bitmap.Info.Width, bitmap.Info.Height, colorType, SKAlphaType.Premul), pixels, bitmap.Info.RowBytes);
surface.Canvas.Flush();

imageSurface?.Dispose();
imageSurface = new ImageSurface(pixels, Format.Argb32, bitmap.Width, bitmap.Height, bitmap.Width * 4);
if (drawable.CanDraw) {
int width = drawable.DrawWidth, height = drawable.DrawHeight;
if (surface == null || imageSurface == null || width != bitmap?.Width || height != bitmap?.Height) {
var colorType = SKImageInfo.PlatformColorType;

bitmap?.Dispose();
bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul);
IntPtr pixels = bitmap.GetPixels();

surface?.Dispose();
surface = SKSurface.Create(new SKImageInfo(bitmap.Info.Width, bitmap.Info.Height, colorType, SKAlphaType.Premul), pixels, bitmap.Info.RowBytes);
surface.Canvas.Flush();

imageSurface?.Dispose();
imageSurface = new ImageSurface(pixels, Format.Argb32, bitmap.Width, bitmap.Height, bitmap.Width * 4);
}

var canvas = surface.Canvas;
using (new SKAutoCanvasRestore(canvas, true)) {
canvas.Clear(drawable.BackgroundColor.ToSkia());
canvas.Translate(-drawable.DrawX, -drawable.DrawY);
drawable.Draw(surface);
}
} else {
drawable.Invalidate();
}

var canvas = surface.Canvas;
using (new SKAutoCanvasRestore(canvas, true)) {
canvas.Clear(drawable.BackgroundColor.ToSkia());
canvas.Translate(-drawable.DrawX, -drawable.DrawY);
drawable.Draw(surface);
if (imageSurface != null) {
args.Cr.SetSourceSurface(imageSurface, drawable.DrawX, drawable.DrawY);
args.Cr.Paint();
}

args.Cr.SetSourceSurface(imageSurface, drawable.DrawX, drawable.DrawY);
args.Cr.Paint();
}
}

Expand Down
67 changes: 37 additions & 30 deletions Studio/CelesteStudio.Mac/SkiaDrawableHandler.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CelesteStudio.Controls;
using CelesteStudio.Util;
using Eto.Forms;
using Eto.Mac.Forms;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
Expand All @@ -8,7 +9,7 @@

namespace CelesteStudio.Mac;

public class SkiaDrawableHandler : MacPanel<SkiaDrawableHandler.SkiaDrawableView, SkiaDrawable, SkiaDrawable.ICallback>, SkiaDrawable.IHandler {
public class SkiaDrawableHandler : MacPanel<SkiaDrawableHandler.SkiaDrawableView, SkiaDrawable, Control.ICallback>, SkiaDrawable.IHandler {
public void Create() {
Enabled = true;
Control = new SkiaDrawableView(Widget);
Expand All @@ -34,38 +35,44 @@ public override void DrawRect(CGRect dirtyRect) {
var bounds = new CGRect(drawable.DrawX * scale, drawable.DrawY * scale, drawable.DrawWidth * scale, drawable.DrawHeight * scale);
var info = new SKImageInfo((int)bounds.Width, (int)bounds.Height, SKColorType.Rgba8888, SKAlphaType.Premul);

// Allocate a memory for the drawing process
nuint infoLength = (nuint)info.BytesSize;
if (surface == null || bitmapData?.Length != infoLength) {
dataProvider?.Dispose();
bitmapData?.Dispose();

bitmapData = NSMutableData.FromLength(infoLength);
dataProvider = new CGDataProvider(bitmapData.MutableBytes, info.BytesSize);

surface?.Dispose();
surface = SKSurface.Create(info, bitmapData.MutableBytes, info.RowBytes);
surface.Canvas.Scale((float)scale);
surface.Canvas.Save();
if (drawable.CanDraw) {
// Allocate a memory for the drawing process
nuint infoLength = (nuint)info.BytesSize;
if (surface == null || bitmapData?.Length != infoLength) {
dataProvider?.Dispose();
bitmapData?.Dispose();

bitmapData = NSMutableData.FromLength(infoLength);
dataProvider = new CGDataProvider(bitmapData.MutableBytes, info.BytesSize);

surface?.Dispose();
surface = SKSurface.Create(info, bitmapData.MutableBytes, info.RowBytes);
surface.Canvas.Scale((float)scale);
surface.Canvas.Save();
}

var canvas = surface.Canvas;
using (new SKAutoCanvasRestore(canvas, true)) {
canvas.Clear(drawable.BackgroundColor.ToSkia());
canvas.Translate(-drawable.DrawX, -drawable.DrawY);
drawable.Draw(surface);
}
canvas.Flush();
} else {
drawable.Invalidate();
}

var canvas = surface.Canvas;
using (new SKAutoCanvasRestore(canvas, true)) {
canvas.Clear(drawable.BackgroundColor.ToSkia());
canvas.Translate(-drawable.DrawX, -drawable.DrawY);
drawable.Draw(surface);
}
canvas.Flush();
if (dataProvider != null) {
using var image = new CGImage(
info.Width, info.Height,
8, info.BitsPerPixel, info.RowBytes,
colorSpace, CGBitmapFlags.ByteOrder32Big | CGBitmapFlags.PremultipliedLast,
dataProvider, null, false, CGColorRenderingIntent.Default);

using var image = new CGImage(
info.Width, info.Height,
8, info.BitsPerPixel, info.RowBytes,
colorSpace, CGBitmapFlags.ByteOrder32Big | CGBitmapFlags.PremultipliedLast,
dataProvider, null, false, CGColorRenderingIntent.Default);

var ctx = NSGraphicsContext.CurrentContext.GraphicsPort;
// NOTE: macOS uses a different coordinate-system
ctx.DrawImage(new CGRect(bounds.X / scale, Bounds.Height - (bounds.Height + bounds.Y) / scale, bounds.Width / scale, bounds.Height / scale), image);
var ctx = NSGraphicsContext.CurrentContext.GraphicsPort;
// NOTE: macOS uses a different coordinate-system
ctx.DrawImage(new CGRect(bounds.X / scale, Bounds.Height - (bounds.Height + bounds.Y) / scale, bounds.Width / scale, bounds.Height / scale), image);
}
}

protected override void Dispose(bool disposing) {
Expand Down
49 changes: 28 additions & 21 deletions Studio/CelesteStudio.WPF/SkiaDrawableHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Eto.Forms;
using Eto.Wpf.Forms;
using SkiaSharp;
using System.Windows.Controls;
using Eto.Wpf;

namespace CelesteStudio.WPF;

public class SkiaDrawableHandler : WpfPanel<Border, SkiaDrawable, SkiaDrawable.ICallback>, SkiaDrawable.IHandler {
public class SkiaDrawableHandler : WpfPanel<Border, SkiaDrawable, Control.ICallback>, SkiaDrawable.IHandler {
public void Create() {
Control = new SkiaBorder(Widget);
}
Expand All @@ -33,29 +34,35 @@ protected override void OnRender(DrawingContext drawingContext) {
return;
}

if (bitmap == null || surface == null || width != bitmap.PixelWidth || height != bitmap.PixelHeight) {
const double bitmapDpi = 96.0;
bitmap = new WriteableBitmap(width, height, bitmapDpi * dpiX, bitmapDpi * dpiY, PixelFormats.Pbgra32, null);

surface?.Dispose();
surface = SKSurface.Create(new SKImageInfo(width, height, SKImageInfo.PlatformColorType, SKAlphaType.Premul), bitmap.BackBuffer, bitmap.BackBufferStride, new SKSurfaceProperties(SKPixelGeometry.Unknown));
surface.Canvas.Scale((float)dpiX, (float)dpiY);
surface.Canvas.Save();
if (drawable.CanDraw) {
if (bitmap == null || surface == null || width != bitmap.PixelWidth || height != bitmap.PixelHeight) {
const double bitmapDpi = 96.0;
bitmap = new WriteableBitmap(width, height, bitmapDpi * dpiX, bitmapDpi * dpiY, PixelFormats.Pbgra32, null);

surface?.Dispose();
surface = SKSurface.Create(new SKImageInfo(width, height, SKImageInfo.PlatformColorType, SKAlphaType.Premul), bitmap.BackBuffer, bitmap.BackBufferStride, new SKSurfaceProperties(SKPixelGeometry.Unknown));
surface.Canvas.Scale((float)dpiX, (float)dpiY);
surface.Canvas.Save();
}

bitmap.Lock();

var canvas = surface.Canvas;
using (new SKAutoCanvasRestore(surface.Canvas, true)) {
canvas.Clear(drawable.BackgroundColor.ToSkia());
canvas.Translate(-drawable.DrawX, -drawable.DrawY);
drawable.Draw(surface);
}
canvas.Flush();
} else {
drawable.Invalidate();
}

bitmap.Lock();

var canvas = surface.Canvas;
using (new SKAutoCanvasRestore(surface.Canvas, true)) {
canvas.Clear(drawable.BackgroundColor.ToSkia());
canvas.Translate(-drawable.DrawX, -drawable.DrawY);
drawable.Draw(surface);
if (bitmap != null) {
bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
drawingContext.DrawImage(bitmap, new Rect(drawable.DrawX, drawable.DrawY, width / dpiX, height / dpiY));
bitmap.Unlock();
}
canvas.Flush();

bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
drawingContext.DrawImage(bitmap, new Rect(drawable.DrawX, drawable.DrawY, width / dpiX, height / dpiY));
bitmap.Unlock();
}

~SkiaBorder() {
Expand Down
50 changes: 4 additions & 46 deletions Studio/CelesteStudio/Controls/SkiaDrawable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,67 +18,25 @@ public SkiaDrawable() {
Initialize();
}

// private readonly SKColorType colorType = Platform.Instance.IsWinForms || Platform.Instance.IsWpf ? SKColorType.Bgra8888 : SKColorType.Rgba8888;

// private Bitmap? image = null;
// private SKImageInfo imageInfo = SKImageInfo.Empty;

// Limits the bounds of the drawn region, for optimization
public virtual int DrawX => 0;
public virtual int DrawY => 0;
public virtual int DrawWidth => Width;
public virtual int DrawHeight => Height;

/// Whether the control can currently be drawn
public virtual bool CanDraw => true;

public bool CanFocus {
get => Handler.CanFocus;
set => Handler.CanFocus = value;
}


public virtual void Draw(SKSurface surface) { }

// protected virtual void OnPaint(PaintEventArgs e)
// {
// try {
// int drawWidth = DrawWidth, drawHeight = DrawHeight;
//
// if (drawWidth <= 0 || drawHeight <= 0) {
// return;
// }
//
// if (drawWidth != image?.Size.Width || drawHeight != image?.Size.Height)
// {
// image?.Dispose();
// image = new Bitmap(new Size(drawWidth, drawHeight), PixelFormat.Format32bppRgba);
// imageInfo = new SKImageInfo(drawWidth, drawHeight, colorType, SKAlphaType.Unpremul);
// }
//
// using var bmp = image.Lock();
// using var surface = SKSurface.Create(imageInfo, bmp.Data, bmp.ScanWidth);
//
// Draw(e, surface, imageInfo);
//
// e.Graphics.DrawImage(image, DrawX, DrawY);
// }
// catch (Exception ex)
// {
// Console.WriteLine(ex);
// e.Graphics.DrawText(Fonts.Monospace(12.0f), Colors.Red, PointF.Empty, ex.ToString());
// }
// }

protected new class Callback : Control.Callback, ICallback {
public void Draw(SkiaDrawable widget, SKSurface surface) {
using (widget.Platform.Context)
widget.Draw(surface);
}
}

[AutoInitialize(false)]
public new interface IHandler : Panel.IHandler {
void Create();
bool CanFocus { get; set; }
}
public new interface ICallback : Control.ICallback {
void Draw(SkiaDrawable widget, SKSurface surface);
}
}
5 changes: 4 additions & 1 deletion Studio/CelesteStudio/Editing/Document.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ public string BackupDirectory {

private readonly Stack<QueuedUpdate> updateStack = [];

/// Whether the document is currently being updated and might not be in a valid state
public bool UpdateInProgress => updateStack.Count != 0;

/// Reports insertions and deletions of the document
public event Action<Document, Dictionary<int, string>, Dictionary<int, string>>? TextChanged;
private void OnTextChanged(Dictionary<int, string> insertions, Dictionary<int, string> deletions)
Expand Down Expand Up @@ -876,7 +879,7 @@ public void ReplaceLines(int row, string[] newLines) {
anchor.OnRemoved?.Invoke();
}
}

// Move anchors below down
if (newLineCount == 0) {
return;
Expand Down
1 change: 1 addition & 0 deletions Studio/CelesteStudio/Editing/Editor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3692,6 +3692,7 @@ private void UpdateMouseCursor(PointF location, Keys modifiers) {
public override int DrawY => scrollablePosition.Y;
public override int DrawWidth => scrollable.Width;
public override int DrawHeight => scrollable.Height;
public override bool CanDraw => !Document.UpdateInProgress;

public override void Draw(SKSurface surface) {
var canvas = surface.Canvas;
Expand Down
6 changes: 5 additions & 1 deletion Studio/CelesteStudio/Editing/GameInfoPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ public void SetupContextMenu(GameInfoPopout? popout = null) {
}

private void RecalcFrameInfo() {
var document = Studio.Instance.Editor.Document;
if (document.UpdateInProgress) {
return;
}

frameInfoBuilder.Clear();

if (CommunicationWrapper.Connected && CommunicationWrapper.CurrentFrameInTas > 0) {
Expand All @@ -263,7 +268,6 @@ private void RecalcFrameInfo() {
}
frameInfoBuilder.Append(Studio.Instance.Editor.TotalFrameCount);

var document = Studio.Instance.Editor.Document;
if (!document.Selection.Empty) {
int minRow = document.Selection.Min.Row;
int maxRow = document.Selection.Max.Row;
Expand Down

0 comments on commit f504d27

Please sign in to comment.