diff --git a/Studio/CelesteStudio.GTK/SkiaDrawableHandler.cs b/Studio/CelesteStudio.GTK/SkiaDrawableHandler.cs index 07664132..f9717870 100644 --- a/Studio/CelesteStudio.GTK/SkiaDrawableHandler.cs +++ b/Studio/CelesteStudio.GTK/SkiaDrawableHandler.cs @@ -1,8 +1,8 @@ using Cairo; using CelesteStudio.Controls; +using CelesteStudio.Util; using Eto.GtkSharp.Forms; using SkiaSharp; -using SkiaSharp.Views.Gtk; using System; namespace CelesteStudio.GTK; @@ -10,8 +10,11 @@ namespace CelesteStudio.GTK; public class SkiaDrawableHandler : GtkPanel, SkiaDrawable.IHandler { private Gtk.Box content = null!; + protected override WeakConnector CreateConnector() => new SkiaDrawableConnector(); + protected new SkiaDrawableConnector Connector => (SkiaDrawableConnector)base.Connector; + public void Create() { - Control = new SkiaEventBox(Widget); + Control = new Gtk.EventBox(); Control.Events |= Gdk.EventMask.ExposureMask; Control.CanFocus = false; Control.CanDefault = true; @@ -21,18 +24,39 @@ public void Create() { Control.Add(content); } - private class SkiaEventBox(SkiaDrawable drawable) : Gtk.EventBox { + protected override void Initialize() { + base.Initialize(); + Control.Drawn += Connector.HandleDrawn; + Control.ButtonPressEvent += Connector.HandleDrawableButtonPressEvent; + } + + protected class SkiaDrawableConnector : GtkPanelEventConnector { + private new SkiaDrawableHandler? Handler => (SkiaDrawableHandler)base.Handler; + private SKBitmap? bitmap; private SKSurface? surface; private ImageSurface? imageSurface; - protected override bool OnDrawn(Context cr) { - if (base.OnDrawn(cr)) { - return true; + public void HandleDrawableButtonPressEvent(object o, Gtk.ButtonPressEventArgs args) { + var handler = Handler; + if (handler == null) { + return; + } + + if (handler.CanFocus) { + handler.Control.GrabFocus(); + } + } + + [GLib.ConnectBefore] + public void HandleDrawn(object o, Gtk.DrawnArgs args) { + if (Handler == null) { + return; } + var drawable = Handler.Widget; int width = drawable.DrawWidth, height = drawable.DrawHeight; - if (width != bitmap?.Width || height != bitmap?.Height) { + if (surface == null || imageSurface == null || width != bitmap?.Width || height != bitmap?.Height) { var colorType = SKImageInfo.PlatformColorType; bitmap?.Dispose(); @@ -47,26 +71,15 @@ protected override bool OnDrawn(Context cr) { imageSurface = new ImageSurface(pixels, Format.Argb32, bitmap.Width, bitmap.Height, bitmap.Width * 4); } - using (new SKAutoCanvasRestore(surface!.Canvas, true)) { + var canvas = surface.Canvas; + using (new SKAutoCanvasRestore(canvas, true)) { + canvas.Clear(drawable.BackgroundColor.ToSkia()); + canvas.Translate(-drawable.DrawX, -drawable.DrawY); drawable.Draw(surface); } - imageSurface!.MarkDirty(); - cr.SetSourceSurface(imageSurface, drawable.DrawX, drawable.DrawY); - cr.Paint(); - - return false; - } - - protected override void Dispose(bool disposing) { - bitmap?.Dispose(); - bitmap = null; - - surface?.Dispose(); - surface = null; - - imageSurface?.Dispose(); - imageSurface = null; + args.Cr.SetSourceSurface(imageSurface, drawable.DrawX, drawable.DrawY); + args.Cr.Paint(); } } @@ -75,8 +88,12 @@ public bool CanFocus { set => Control.CanFocus = value; } - protected override void SetContainerContent(Gtk.Widget containerContent) - { + protected override void SetContainerContent(Gtk.Widget containerContent) { content.Add(containerContent); } + + protected override void SetBackgroundColor(Eto.Drawing.Color? color) { + // Handled by ourselves + Invalidate(false); + } } diff --git a/Studio/CelesteStudio.Mac/SkiaDrawableHandler.cs b/Studio/CelesteStudio.Mac/SkiaDrawableHandler.cs index 6cfc64b4..86db2e5a 100644 --- a/Studio/CelesteStudio.Mac/SkiaDrawableHandler.cs +++ b/Studio/CelesteStudio.Mac/SkiaDrawableHandler.cs @@ -1,4 +1,5 @@ using CelesteStudio.Controls; +using CelesteStudio.Util; using Eto.Mac.Forms; using MonoMac.AppKit; using MonoMac.CoreGraphics; @@ -35,7 +36,7 @@ public override void DrawRect(CGRect dirtyRect) { // Allocate a memory for the drawing process nuint infoLength = (nuint)info.BytesSize; - if (bitmapData?.Length != infoLength) { + if (surface == null || bitmapData?.Length != infoLength) { dataProvider?.Dispose(); bitmapData?.Dispose(); @@ -46,10 +47,13 @@ public override void DrawRect(CGRect dirtyRect) { surface = SKSurface.Create(info, bitmapData.MutableBytes, info.RowBytes); } - using (new SKAutoCanvasRestore(surface!.Canvas, true)) { + var canvas = surface.Canvas; + using (new SKAutoCanvasRestore(canvas, true)) { + canvas.Clear(drawable.BackgroundColor.ToSkia()); + canvas.Translate(-drawable.DrawX, -drawable.DrawY); drawable.Draw(surface); } - surface.Canvas.Flush(); + canvas.Flush(); using var image = new CGImage( info.Width, info.Height, diff --git a/Studio/CelesteStudio.WPF/SkiaDrawableHandler.cs b/Studio/CelesteStudio.WPF/SkiaDrawableHandler.cs index e1247f7e..86a6137e 100644 --- a/Studio/CelesteStudio.WPF/SkiaDrawableHandler.cs +++ b/Studio/CelesteStudio.WPF/SkiaDrawableHandler.cs @@ -1,4 +1,5 @@ using CelesteStudio.Controls; +using CelesteStudio.Util; using System; using System.Windows; using System.Windows.Media; @@ -44,10 +45,13 @@ protected override void OnRender(DrawingContext drawingContext) { 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); } - surface.Canvas.Flush(); + canvas.Flush(); bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height)); drawingContext.DrawImage(bitmap, new Rect(drawable.DrawX, drawable.DrawY, width / dpiX, height / dpiY)); diff --git a/Studio/CelesteStudio/Dialog/ChangelogDialog.cs b/Studio/CelesteStudio/Dialog/ChangelogDialog.cs index cd1ed622..958108a1 100644 --- a/Studio/CelesteStudio/Dialog/ChangelogDialog.cs +++ b/Studio/CelesteStudio/Dialog/ChangelogDialog.cs @@ -20,7 +20,7 @@ private class ContentDrawable(string title, Dictionary> cat : SystemColors.ControlText.ToSkia(); public override void Draw(SKSurface surface) { - surface.Canvas.Clear(); + var canvas = surface.Canvas; var textColor = TextColor; @@ -46,27 +46,27 @@ public override void Draw(SKSurface surface) { float yOffset = 0.0f; // Title foreach (string line in WrapLines(title, Width, titlePaint)) { - surface.Canvas.DrawText(line, Width / 2.0f, yOffset + titleFont.Offset(), titlePaint); + canvas.DrawText(line, Width / 2.0f, yOffset + titleFont.Offset(), titlePaint); yOffset += titleFont.LineHeight(); } yOffset += titleFont.LineHeight() * 0.25f; - surface.Canvas.DrawLine(0.0f, yOffset, Width, yOffset, titlePaint); + canvas.DrawLine(0.0f, yOffset, Width, yOffset, titlePaint); yOffset += titleFont.LineHeight() * 0.5f; foreach ((string categoryName, var entries) in categories) { // Heading foreach (string line in WrapLines(categoryName, Width, headingPaint)) { - surface.Canvas.DrawText(line, 0.0f, yOffset + headingFont.Offset(), headingPaint); + canvas.DrawText(line, 0.0f, yOffset + headingFont.Offset(), headingPaint); yOffset += headingFont.LineHeight(); } yOffset += headingFont.LineHeight() * 0.5f; // Entries foreach (string entry in entries) { - surface.Canvas.DrawCircle(10.0f, yOffset + entryFont.LineHeight() / 2.0f, 2.5f, entryPaint); + canvas.DrawCircle(10.0f, yOffset + entryFont.LineHeight() / 2.0f, 2.5f, entryPaint); foreach (string line in WrapLines(entry, Width, entryPaint)) { - surface.Canvas.DrawText(line, 20.0f, yOffset + entryFont.Offset(), entryPaint); + canvas.DrawText(line, 20.0f, yOffset + entryFont.Offset(), entryPaint); yOffset += entryFont.LineHeight(); } } diff --git a/Studio/CelesteStudio/Dialog/FontDialog.cs b/Studio/CelesteStudio/Dialog/FontDialog.cs index d1541e3b..f1e4e34a 100644 --- a/Studio/CelesteStudio/Dialog/FontDialog.cs +++ b/Studio/CelesteStudio/Dialog/FontDialog.cs @@ -32,7 +32,7 @@ public FontPreview() { } public override void Draw(SKSurface surface) { - surface.Canvas.Clear(); + var canvas = surface.Canvas; if (highlighter == null) return; @@ -50,7 +50,7 @@ public override void Draw(SKSurface surface) { float yPos = 0.0f; float maxWidth = 0.0f; foreach (var line in previewText) { - highlighter.DrawLine(surface.Canvas, 0.0f, yPos, line); + highlighter.DrawLine(canvas, 0.0f, yPos, line); maxWidth = Math.Max(maxWidth, font.MeasureWidth(line)); yPos += font.LineHeight(); } diff --git a/Studio/CelesteStudio/Editing/Editor.cs b/Studio/CelesteStudio/Editing/Editor.cs index 2649ee47..8fa9e827 100644 --- a/Studio/CelesteStudio/Editing/Editor.cs +++ b/Studio/CelesteStudio/Editing/Editor.cs @@ -3626,8 +3626,6 @@ private void UpdateMouseCursor(PointF location, Keys modifiers) { public override void Draw(SKSurface surface) { var canvas = surface.Canvas; - canvas.Clear(); - canvas.Translate(-scrollablePosition.X, -scrollablePosition.Y); using var strokePaint = new SKPaint(); strokePaint.Style = SKPaintStyle.Stroke; diff --git a/Studio/CelesteStudio/Editing/GameInfoPanel.cs b/Studio/CelesteStudio/Editing/GameInfoPanel.cs index 03927fb6..6e817005 100644 --- a/Studio/CelesteStudio/Editing/GameInfoPanel.cs +++ b/Studio/CelesteStudio/Editing/GameInfoPanel.cs @@ -15,7 +15,7 @@ namespace CelesteStudio.Editing; public sealed class GameInfo : Panel { private sealed class SubpixelIndicator : SkiaDrawable { public override void Draw(SKSurface surface) { - surface.Canvas.Clear(); + var canvas = surface.Canvas; var remainder = CommunicationWrapper.PlayerPositionRemainder; @@ -49,11 +49,11 @@ public override void Draw(SKSurface surface) { string top = subpixelTop.ToFormattedString(vDecimals); string bottom = subpixelBottom.ToFormattedString(vDecimals); - surface.Canvas.DrawText(left, x - rectPadding - font.MeasureWidth(left), y + (rectSize - textHeight) / 2.0f + font.Offset(), font, Settings.Instance.Theme.StatusFgPaint); - surface.Canvas.DrawText(right, x + rectPadding + rectSize, y + (rectSize - textHeight) / 2.0f + font.Offset(), font, Settings.Instance.Theme.StatusFgPaint); + canvas.DrawText(left, x - rectPadding - font.MeasureWidth(left), y + (rectSize - textHeight) / 2.0f + font.Offset(), font, Settings.Instance.Theme.StatusFgPaint); + canvas.DrawText(right, x + rectPadding + rectSize, y + (rectSize - textHeight) / 2.0f + font.Offset(), font, Settings.Instance.Theme.StatusFgPaint); - surface.Canvas.DrawText(top, MathF.Round(x + (rectSize - font.MeasureWidth(top)) / 2.0f), MathF.Round(y - rectPadding - textHeight + font.Offset()), font, Settings.Instance.Theme.StatusFgPaint); - surface.Canvas.DrawText(bottom, x + (rectSize - font.MeasureWidth(bottom)) / 2.0f, y + rectPadding + rectSize + font.Offset(), font, Settings.Instance.Theme.StatusFgPaint); + canvas.DrawText(top, MathF.Round(x + (rectSize - font.MeasureWidth(top)) / 2.0f), MathF.Round(y - rectPadding - textHeight + font.Offset()), font, Settings.Instance.Theme.StatusFgPaint); + canvas.DrawText(bottom, x + (rectSize - font.MeasureWidth(bottom)) / 2.0f, y + rectPadding + rectSize + font.Offset(), font, Settings.Instance.Theme.StatusFgPaint); int boxThickness = Math.Max(1, (int)Math.Round(rectSize / 20.0f)); float dotThickness = boxThickness * 1.25f; @@ -63,8 +63,8 @@ public override void Draw(SKSurface surface) { boxPaint.Style = SKPaintStyle.Stroke; boxPaint.StrokeWidth = boxThickness; - surface.Canvas.DrawRect(x, y, rectSize, rectSize, boxPaint); - surface.Canvas.DrawRect(x + (rectSize - dotThickness) * subpixelLeft, y + (rectSize - dotThickness) * subpixelTop, dotThickness, dotThickness, Settings.Instance.Theme.SubpixelIndicatorDotPaint); + canvas.DrawRect(x, y, rectSize, rectSize, boxPaint); + canvas.DrawRect(x + (rectSize - dotThickness) * subpixelLeft, y + (rectSize - dotThickness) * subpixelTop, dotThickness, dotThickness, Settings.Instance.Theme.SubpixelIndicatorDotPaint); Width = (int)((textWidth + rectPadding + indicatorPadding) * 2.0f + rectSize); Height = (int)((textHeight + rectPadding + indicatorPadding) * 2.0f + rectSize); diff --git a/Studio/CelesteStudio/Editing/PopupMenu.cs b/Studio/CelesteStudio/Editing/PopupMenu.cs index 9613541e..b36f95ab 100644 --- a/Studio/CelesteStudio/Editing/PopupMenu.cs +++ b/Studio/CelesteStudio/Editing/PopupMenu.cs @@ -50,16 +50,15 @@ public ContentDrawable(PopupMenu menu) { public override int DrawHeight => menu.Height; public override void Draw(SKSurface surface) { - surface.Canvas.Clear(); - surface.Canvas.Translate(-menu.ScrollPosition.X, -menu.ScrollPosition.Y); + var canvas = surface.Canvas; if (menu.shownEntries.Length == 0) { return; } var backgroundRect = new SKRect(menu.ScrollPosition.X, menu.ScrollPosition.Y, menu.ScrollPosition.X + menu.Width, menu.ScrollPosition.Y + menu.Height); - surface.Canvas.ClipRoundRect(new SKRoundRect(backgroundRect, Settings.Instance.Theme.PopupMenuBorderRounding), antialias: true); - surface.Canvas.DrawRect(backgroundRect, Settings.Instance.Theme.PopupMenuBgPaint); + canvas.ClipRoundRect(new SKRoundRect(backgroundRect, Settings.Instance.Theme.PopupMenuBorderRounding), antialias: true); + canvas.DrawRect(backgroundRect, Settings.Instance.Theme.PopupMenuBgPaint); var font = FontManager.SKPopupFont; int maxDisplayLen = menu.shownEntries.Select(entry => entry.DisplayText.Length).Aggregate(Math.Max); @@ -76,7 +75,7 @@ public override void Draw(SKSurface surface) { // Highlight selected entry if (row == menu.SelectedEntry && !entry.Disabled) { - surface.Canvas.DrawRoundRect( + canvas.DrawRoundRect( x: Settings.Instance.Theme.PopupMenuBorderPadding, y: row * height + Settings.Instance.Theme.PopupMenuBorderPadding + Settings.Instance.Theme.PopupMenuEntrySpacing / 2.0f, w: width, @@ -86,11 +85,11 @@ public override void Draw(SKSurface surface) { Settings.Instance.Theme.PopupMenuSelectedPaint); } - surface.Canvas.DrawText(entry.DisplayText, + canvas.DrawText(entry.DisplayText, x: Settings.Instance.Theme.PopupMenuBorderPadding + Settings.Instance.Theme.PopupMenuEntryHorizontalPadding, y: Settings.Instance.Theme.PopupMenuBorderPadding + row * height + Settings.Instance.Theme.PopupMenuEntryVerticalPadding + Settings.Instance.Theme.PopupMenuEntrySpacing / 2.0f + font.Offset(), font, entry.Disabled ? Settings.Instance.Theme.PopupMenuFgDisabledPaint : Settings.Instance.Theme.PopupMenuFgPaint); - surface.Canvas.DrawText(entry.ExtraText, + canvas.DrawText(entry.ExtraText, x: Settings.Instance.Theme.PopupMenuBorderPadding + Settings.Instance.Theme.PopupMenuEntryHorizontalPadding + font.CharWidth() * (maxDisplayLen + DisplayExtraPadding), y: Settings.Instance.Theme.PopupMenuBorderPadding + row * height + Settings.Instance.Theme.PopupMenuEntryVerticalPadding + Settings.Instance.Theme.PopupMenuEntrySpacing / 2.0f + font.Offset(), font, Settings.Instance.Theme.PopupMenuFgExtraPaint);