diff --git a/Studio/CelesteStudio/Editing/Editor.cs b/Studio/CelesteStudio/Editing/Editor.cs index d0d6a3e1..07e5a165 100644 --- a/Studio/CelesteStudio/Editing/Editor.cs +++ b/Studio/CelesteStudio/Editing/Editor.cs @@ -3546,7 +3546,7 @@ protected override void OnMouseWheel(MouseEventArgs e) { private void UpdateMouseCursor(PointF location, Keys modifiers) { // Maybe a bit out-of-place here, but this is required to update the underline of line links - Invalidate(); + // Invalidate(); if (modifiers.HasCommonModifier() && LocationToLineLink(location) != null) { Cursor = Cursors.Pointer; @@ -3624,8 +3624,12 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im var canvas = surface.Canvas; canvas.Clear(); - // Adjust unit to points instead of pixels - //canvas.Scale(e.Graphics.PixelsPerPoint); + using var strokePaint = new SKPaint(); + strokePaint.Style = SKPaintStyle.Stroke; + + using var fillPaint = new SKPaint(); + fillPaint.Style = SKPaintStyle.Fill; + fillPaint.IsAntialias = true; // To be reused below. Kinda annoying how C# handles out parameter conflicts WrapEntry wrap; @@ -3638,10 +3642,6 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im int bottomRow = Math.Min(Document.Lines.Count - 1, GetActualRow(bottomVisualRow)); // Draw text - using var commentPaint = new SKPaint(); - commentPaint.Color = Settings.Instance.Theme.Comment.ForegroundColor.ToSkia(); - commentPaint.IsAntialias = true; - float yPos = actualToVisualRows[topRow] * Font.LineHeight(); for (int row = topRow; row <= bottomRow; row++) { string line = Document.Lines[row]; @@ -3656,7 +3656,7 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im var subLine = wrap.Lines[i].Line; float xIdent = i == 0 ? 0 : wrap.StartOffset * Font.CharWidth(); - canvas.DrawText(subLine, textOffsetX + xIdent, yPos, Font, commentPaint); + canvas.DrawText(subLine, textOffsetX + xIdent, yPos, Font, Settings.Instance.Theme.CommentPaint.ForegroundColor); yPos += Font.LineHeight(); width = Math.Max(width, Font.MeasureWidth(subLine) + xIdent); height += Font.LineHeight(); @@ -3668,15 +3668,12 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im height = Font.LineHeight(); } - using var boxPaint = new SKPaint(); - boxPaint.Color = commentPaint.Color; - boxPaint.Style = SKPaintStyle.Stroke; canvas.DrawRect( x: Font.CharWidth() * collapse.StartCol + textOffsetX - foldingPadding, y: yPos - height - foldingPadding, w: width - Font.CharWidth() * collapse.StartCol + foldingPadding * 2.0f, h: height + foldingPadding * 2.0f, - boxPaint); + Settings.Instance.Theme.CommentBoxPaint); row = collapse.MaxRow; continue; @@ -3698,7 +3695,7 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im var subLine = wrap.Lines[i].Line; float xIdent = i == 0 ? 0 : wrap.StartOffset * Font.CharWidth(); - canvas.DrawText(subLine, textOffsetX + xIdent, yPos, Font, commentPaint); + canvas.DrawText(subLine, textOffsetX + xIdent, yPos, Font, Settings.Instance.Theme.CommentPaint.ForegroundColor); yPos += Font.LineHeight(); } } else { @@ -3723,6 +3720,7 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im boxPaint.Color = selected ? SKColors.White : SKColors.Gray; boxPaint.StrokeWidth = selected ? 2.0f : 1.0f; boxPaint.Style = SKPaintStyle.Stroke; + canvas.DrawRect( x + textOffsetX - padding, y - padding, w + padding * 2.0f, Font.LineHeight() + padding * 2.0f, boxPaint); @@ -3738,13 +3736,11 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im const float padding = 10.0f; float suffixWidth = font.MeasureWidth(CommunicationWrapper.CurrentLineSuffix); - using var suffixPaint = new SKPaint(); - suffixPaint.Color = Settings.Instance.Theme.PlayingFrame.ToSkia(); - suffixPaint.IsAntialias = true; + fillPaint.ColorF = Settings.Instance.Theme.PlayingFrame.ToSkia(); canvas.DrawText(CommunicationWrapper.CurrentLineSuffix, x: scrollablePosition.X + scrollableSize.Width - suffixWidth - padding, y: actualToVisualRows[CommunicationWrapper.CurrentLine] * font.LineHeight()+ Font.Offset(), - suffixPaint); + fillPaint); } var caretPos = GetVisualPosition(Document.Caret); @@ -3752,42 +3748,33 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im float carY = Font.LineHeight() * caretPos.Row; // Highlight caret line - using var highlightPaint = new SKPaint(); - highlightPaint.Color = Settings.Instance.Theme.CurrentLine.ToSkia(); - highlightPaint.Style = SKPaintStyle.Fill; - highlightPaint.IsAntialias = true; + fillPaint.ColorF = Settings.Instance.Theme.CurrentLine.ToSkia(); canvas.DrawRect( x: scrollablePosition.X, y: carY, w: scrollable.Width, h: Font.LineHeight(), - highlightPaint); + fillPaint); // Draw caret if (HasFocus) { - using var caretPaint = new SKPaint(); - caretPaint.Color = Settings.Instance.Theme.Caret.ToSkia(); - caretPaint.Style = SKPaintStyle.Stroke; - caretPaint.IsAntialias = true; - canvas.DrawLine(carX, carY, carX, carY + Font.LineHeight() - 1, caretPaint); + strokePaint.ColorF = Settings.Instance.Theme.Caret.ToSkia(); + canvas.DrawLine(carX, carY, carX, carY + Font.LineHeight() - 1, strokePaint); } // Draw selection if (!Document.Selection.Empty) { - using var selectionPaint = new SKPaint(); - selectionPaint.Color = Settings.Instance.Theme.Selection.ToSkia(); - selectionPaint.Style = SKPaintStyle.Fill; - selectionPaint.IsAntialias = true; - var min = GetVisualPosition(Document.Selection.Min); var max = GetVisualPosition(Document.Selection.Max); + fillPaint.ColorF = Settings.Instance.Theme.Selection.ToSkia(); + if (min.Row == max.Row) { float x = Font.CharWidth() * min.Col + textOffsetX; float w = Font.CharWidth() * (max.Col - min.Col); float y = Font.LineHeight() * min.Row; float h = Font.LineHeight(); - canvas.DrawRect(x, y, w, h, selectionPaint); + canvas.DrawRect(x, y, w, h, fillPaint); } else { var visualLine = GetVisualLine(min.Row); @@ -3796,33 +3783,24 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im float x = Font.CharWidth() * min.Col + textOffsetX - extendLeft; float w = visualLine.Length == 0 ? 0.0f : Font.MeasureWidth(visualLine[min.Col..]); float y = Font.LineHeight() * min.Row; - canvas.DrawRect(x, y, w + extendLeft, Font.LineHeight(), selectionPaint); + canvas.DrawRect(x, y, w + extendLeft, Font.LineHeight(), fillPaint); // Cull off-screen lines for (int i = Math.Max(min.Row + 1, topVisualRow); i < Math.Min(max.Row, bottomVisualRow); i++) { // Draw at least half a character for each line w = Font.CharWidth() * Math.Max(0.5f, GetVisualLine(i).Length); y = Font.LineHeight() * i; - canvas.DrawRect(textOffsetX - LineNumberPadding, y, w + LineNumberPadding, Font.LineHeight(), selectionPaint); + canvas.DrawRect(textOffsetX - LineNumberPadding, y, w + LineNumberPadding, Font.LineHeight(), fillPaint); } w = Font.MeasureWidth(GetVisualLine(max.Row)[..max.Col]); y = Font.LineHeight() * max.Row; - canvas.DrawRect(textOffsetX - LineNumberPadding, y, w + LineNumberPadding, Font.LineHeight(), selectionPaint); + canvas.DrawRect(textOffsetX - LineNumberPadding, y, w + LineNumberPadding, Font.LineHeight(), fillPaint); } } // Draw calculate operation if (calculationState is not null) { - using var calcFgPaint = new SKPaint(); - calcFgPaint.Color = Settings.Instance.Theme.CalculateFg.ToSkia(); - calcFgPaint.Style = SKPaintStyle.Fill; - calcFgPaint.IsAntialias = true; - using var calcBgPaint = new SKPaint(); - calcBgPaint.Color = Settings.Instance.Theme.CalculateBg.ToSkia(); - calcBgPaint.Style = SKPaintStyle.Fill; - calcBgPaint.IsAntialias = true; - string calculateLine = $"{calculationState.Operator.Char()}{calculationState.Operand}"; float padding = Font.CharWidth() * 0.5f; @@ -3831,69 +3809,55 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im float w = Font.CharWidth() * calculateLine.Length + 2 * padding; float h = Font.LineHeight(); - canvas.DrawRoundRect(x, y, w, h, 4.0f, 4.0f, calcBgPaint); - canvas.DrawText(calculateLine, x + padding, y+ Font.Offset(), Font, calcFgPaint); + fillPaint.ColorF = Settings.Instance.Theme.CalculateBg.ToSkia(); + canvas.DrawRoundRect(x, y, w, h, 4.0f, 4.0f, fillPaint); + fillPaint.ColorF = Settings.Instance.Theme.CalculateFg.ToSkia(); + canvas.DrawText(calculateLine, x + padding, y+ Font.Offset(), Font, fillPaint); } // Draw line numbers { - using var lineBgPaint = new SKPaint(); - lineBgPaint.Color = Settings.Instance.Theme.Background.ToSkia(); - lineBgPaint.Style = SKPaintStyle.Fill; - lineBgPaint.IsAntialias = true; - + fillPaint.ColorF = Settings.Instance.Theme.Background.ToSkia(); canvas.DrawRect( x: scrollablePosition.X, y: scrollablePosition.Y, w: textOffsetX - LineNumberPadding, h: scrollableSize.Height, - lineBgPaint); + fillPaint); // Highlight playing / savestate line if (CommunicationWrapper.Connected) { if (CommunicationWrapper.CurrentLine != -1 && CommunicationWrapper.CurrentLine < actualToVisualRows.Length) { - using var playingPaint = new SKPaint(); - playingPaint.Color = Settings.Instance.Theme.Background.ToSkia(); - playingPaint.Style = SKPaintStyle.Fill; - playingPaint.IsAntialias = true; - + fillPaint.ColorF = Settings.Instance.Theme.PlayingLineBg.ToSkia(); canvas.DrawRect( x: scrollablePosition.X, y: actualToVisualRows[CommunicationWrapper.CurrentLine] * Font.LineHeight(), w: textOffsetX - LineNumberPadding, h: Font.LineHeight(), - playingPaint); + fillPaint); } if (CommunicationWrapper.SaveStateLine != -1 && CommunicationWrapper.SaveStateLine < actualToVisualRows.Length) { - using var savestatePaint = new SKPaint(); - savestatePaint.Color = Settings.Instance.Theme.Background.ToSkia(); - savestatePaint.Style = SKPaintStyle.Fill; - savestatePaint.IsAntialias = true; - + fillPaint.ColorF = Settings.Instance.Theme.SavestateBg.ToSkia(); if (CommunicationWrapper.SaveStateLine == CommunicationWrapper.CurrentLine) { canvas.DrawRect( x: scrollablePosition.X, y: actualToVisualRows[CommunicationWrapper.SaveStateLine] * Font.LineHeight(), w: 5.0f, h: Font.LineHeight(), - savestatePaint); + fillPaint); } else { canvas.DrawRect( x: scrollablePosition.X, y: actualToVisualRows[CommunicationWrapper.SaveStateLine] * Font.LineHeight(), w: textOffsetX - LineNumberPadding, h: Font.LineHeight(), - savestatePaint); + fillPaint); } } } yPos = actualToVisualRows[topRow] * Font.LineHeight(); - using var textPaint = new SKPaint(); - textPaint.Style = SKPaintStyle.Fill; - textPaint.IsAntialias = true; - for (int row = topRow; row <= bottomRow; row++) { int oldRow = row; var numberString = (row + 1).ToString(); @@ -3903,17 +3867,19 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im bool isSaveStateLine = CommunicationWrapper.SaveStateLine >= 0 && CommunicationWrapper.SaveStateLine < actualToVisualRows.Length && actualToVisualRows[CommunicationWrapper.SaveStateLine] == actualToVisualRows[row]; - textPaint.Color = isPlayingLine - ? Settings.Instance.Theme.PlayingLineFg.ToSkia() - : isSaveStateLine - ? Settings.Instance.Theme.SavestateFg.ToSkia() - : Settings.Instance.Theme.LineNumber.ToSkia(); + if (isPlayingLine) { + fillPaint.ColorF = Settings.Instance.Theme.PlayingLineFg.ToSkia(); + } else if (isSaveStateLine) { + fillPaint.ColorF = Settings.Instance.Theme.SavestateFg.ToSkia(); + } else { + fillPaint.ColorF = Settings.Instance.Theme.LineNumber.ToSkia(); + } if (Settings.Instance.LineNumberAlignment == LineNumberAlignment.Left) { - canvas.DrawText(numberString, scrollablePosition.X + LineNumberPadding, yPos+ Font.Offset(), Font, textPaint); + canvas.DrawText(numberString, scrollablePosition.X + LineNumberPadding, yPos+ Font.Offset(), Font, fillPaint); } else if (Settings.Instance.LineNumberAlignment == LineNumberAlignment.Right) { float ident = Font.CharWidth() * (Document.Lines.Count.Digits() - (row + 1).Digits()); - canvas.DrawText(numberString, scrollablePosition.X + LineNumberPadding + ident, yPos+ Font.Offset(), Font, textPaint); + canvas.DrawText(numberString, scrollablePosition.X + LineNumberPadding + ident, yPos+ Font.Offset(), Font, fillPaint); } bool collapsed = false; @@ -3928,7 +3894,7 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im dy: yPos + (Font.LineHeight() - Font.CharWidth()) / 2.0f); canvas.Scale(Font.CharWidth()); - canvas.DrawPath(collapsed ? Assets.CollapseClosedPath : Assets.CollapseOpenPath, textPaint); + canvas.DrawPath(collapsed ? Assets.CollapseClosedPath : Assets.CollapseOpenPath, fillPaint); canvas.Restore(); } @@ -3940,14 +3906,11 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im } } - using var separatorPaint = new SKPaint(); - separatorPaint.Color = Settings.Instance.Theme.ServiceLine.ToSkia(); - separatorPaint.Style = SKPaintStyle.Stroke; - separatorPaint.IsAntialias = true; + strokePaint.ColorF = Settings.Instance.Theme.ServiceLine.ToSkia(); canvas.DrawLine( x0: scrollablePosition.X + textOffsetX - LineNumberPadding, y0: 0.0f, x1: scrollablePosition.X + textOffsetX - LineNumberPadding, y1: yPos + scrollableSize.Height, - separatorPaint); + strokePaint); } // Draw toast message box @@ -3959,25 +3922,19 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im float x = scrollablePosition.X + (scrollableSize.Width - width) / 2.0f; float y = scrollablePosition.Y + (scrollableSize.Height - height) / 2.0f; - using var popupBgPaint = new SKPaint(); - popupBgPaint.Color = Settings.Instance.Theme.PopupMenuBg.ToSkia(); - popupBgPaint.Style = SKPaintStyle.Fill; - popupBgPaint.IsAntialias = true; - float padding = Settings.Instance.Theme.PopupMenuBorderPadding; + + fillPaint.ColorF = Settings.Instance.Theme.PopupMenuBg.ToSkia(); canvas.DrawRoundRect( x: x - padding, y: y - padding, w: width + padding * 2.0f, h: height + padding * 2.0f, rx: Settings.Instance.Theme.PopupMenuBorderRounding, ry: Settings.Instance.Theme.PopupMenuBorderRounding, - popupBgPaint); + fillPaint); - using var popupFgPaint = new SKPaint(); - popupFgPaint.Color = Settings.Instance.Theme.PopupMenuFg.ToSkia(); - popupFgPaint.Style = SKPaintStyle.Fill; - popupFgPaint.IsAntialias = true; + fillPaint.ColorF = Settings.Instance.Theme.PopupMenuFg.ToSkia(); foreach (var line in lines) { // TODO: Use PopupFont - canvas.DrawText(line, x, y+ Font.Offset(), Font, popupFgPaint); + canvas.DrawText(line, x, y+ Font.Offset(), Font, fillPaint); y += Font.LineHeight(); } } diff --git a/Studio/CelesteStudio/Editing/SyntaxHighlighter.cs b/Studio/CelesteStudio/Editing/SyntaxHighlighter.cs index 5de2fdcf..4086945b 100644 --- a/Studio/CelesteStudio/Editing/SyntaxHighlighter.cs +++ b/Studio/CelesteStudio/Editing/SyntaxHighlighter.cs @@ -32,7 +32,7 @@ public class SyntaxHighlighter { private const int MaxCacheSize = 32767; private readonly Dictionary cache = new(); - private Style[] styles = []; + private StylePaint[] styles = []; private readonly Font regularFont; private readonly Font boldFont; @@ -52,7 +52,7 @@ public SyntaxHighlighter(Font regularFont, Font boldFont, Font italicFont, Font private void LoadTheme(Theme theme) { cache.Clear(); // IMPORTANT: Must be the same order as the StyleType enum! - styles = [theme.Action, theme.Angle, theme.Breakpoint, theme.SavestateBreakpoint, theme.Delimiter, theme.Command, theme.Comment, theme.Frame]; + styles = [theme.ActionPaint, theme.AnglePaint, theme.BreakpointPaint, theme.SavestateBreakpointPaint, theme.DelimiterPaint, theme.CommandPaint, theme.CommentPaint, theme.FramePaint]; } public struct DrawLineOptions() { @@ -91,11 +91,7 @@ public void DrawLine(SKCanvas canvas, float x, float y, string line, DrawLineOpt float width = font.MeasureWidth(str); if (style.BackgroundColor is { } bgColor) { - using var paint = new SKPaint(); - paint.Color = bgColor.ToSkia(); - paint.Style = SKPaintStyle.Fill; - paint.IsAntialias = true; - canvas.DrawRect(x + xOff, y, width, font.LineHeight(), paint); + canvas.DrawRect(x + xOff, y, width, font.LineHeight(), bgColor); } int underlineStart = Math.Max(segment.StartIdx, options.Value.UnderlineStart); @@ -114,12 +110,7 @@ public void DrawLine(SKCanvas canvas, float x, float y, string line, DrawLineOpt // canvas.DrawText(underlineFont, style.ForegroundColor, x + xOff + font.MeasureWidth(left), y, middle); // canvas.DrawText(font, style.ForegroundColor, x + xOff + font.MeasureWidth(left) + underlineFont.MeasureWidth(middle), y, right); // } else { - using var textPaint = new SKPaint(); - textPaint.Color = style.ForegroundColor.ToSkia(); - // textPaint.Style = SKPaintStyle.Fill; - textPaint.IsAntialias = true; - textPaint.TextAlign = SKTextAlign.Left; - canvas.DrawText(str, x + xOff, y + font.Offset(), font, textPaint); + canvas.DrawText(str, x + xOff, y + font.Offset(), font, style.ForegroundColor); // } xOff += width; diff --git a/Studio/CelesteStudio/Editing/Theme.cs b/Studio/CelesteStudio/Editing/Theme.cs index b50efd86..cf6774b0 100644 --- a/Studio/CelesteStudio/Editing/Theme.cs +++ b/Studio/CelesteStudio/Editing/Theme.cs @@ -1,14 +1,33 @@ +using CelesteStudio.Util; using Eto.Drawing; +using SkiaSharp; +using System; using System.Collections.Generic; namespace CelesteStudio.Editing; public struct Style(Color foregroundColor, Color? backgroundColor = null, FontStyle fontStyle = FontStyle.None) { - // TODO: Convert to preferably SKPaint, otherwise SKColor public Color ForegroundColor = foregroundColor; public Color? BackgroundColor = backgroundColor; public FontStyle FontStyle = fontStyle; + + public StylePaint CreatePaint() => new(CreateForegroundPaint(), CreateBackgroundPaint()); + + public SKPaint CreateForegroundPaint(SKPaintStyle style = SKPaintStyle.Fill) => + new() { ColorF = ForegroundColor.ToSkia(), Style = style, IsAntialias = true }; + public SKPaint? CreateBackgroundPaint(SKPaintStyle style = SKPaintStyle.Fill) => + BackgroundColor == null ? null : new() { ColorF = BackgroundColor.Value.ToSkia(), Style = style, IsAntialias = true }; +} + +public readonly struct StylePaint(SKPaint foregroundColor, SKPaint? backgroundColor = null) : IDisposable { + public readonly SKPaint ForegroundColor = foregroundColor; + public readonly SKPaint? BackgroundColor = backgroundColor; + + public void Dispose() { + ForegroundColor.Dispose(); + BackgroundColor?.Dispose(); + } } public struct Theme { @@ -59,9 +78,23 @@ public struct Theme { public Style Frame; public Style Comment; + // Cache SKPaints + private StylePaint? _actionPaint, _anglePaint, _breakpointPaint, _savestateBreakpointPaint, _delimiter, _command, _frame, _comment; + private SKPaint? _commentBox; + + public StylePaint ActionPaint => _actionPaint ??= Action.CreatePaint(); + public StylePaint AnglePaint => _anglePaint ??= Angle.CreatePaint(); + public StylePaint BreakpointPaint => _breakpointPaint ??= Breakpoint.CreatePaint(); + public StylePaint SavestateBreakpointPaint => _savestateBreakpointPaint ??= SavestateBreakpoint.CreatePaint(); + public StylePaint DelimiterPaint => _delimiter ??= Delimiter.CreatePaint(); + public StylePaint CommandPaint => _command ??= Command.CreatePaint(); + public StylePaint FramePaint => _frame ??= Frame.CreatePaint(); + public StylePaint CommentPaint => _comment ??= Comment.CreatePaint(); + public SKPaint CommentBoxPaint => _commentBox ??= Comment.CreateForegroundPaint(SKPaintStyle.Stroke); + public bool DarkMode; - public Theme() {} + public Theme() { } public const string BuiltinLight = "Light"; public const string BuiltinDark = "Dark"; diff --git a/Studio/CelesteStudio/Util/Extensions.cs b/Studio/CelesteStudio/Util/Extensions.cs index 65a1c49b..2e29610a 100644 --- a/Studio/CelesteStudio/Util/Extensions.cs +++ b/Studio/CelesteStudio/Util/Extensions.cs @@ -94,9 +94,8 @@ public static int IndexOf(this IEnumerable obj, T value, IEqualityComparer return -1; } - public static SKColor ToSkia(this Color color) { - return new SKColor((byte)(color.R * byte.MaxValue), (byte)(color.G * byte.MaxValue), (byte)(color.B * byte.MaxValue), (byte)(color.A * byte.MaxValue)); - } + public static SKColorF ToSkia(this Color color) => new(color.R, color.G, color.B, color.A); + public static Color ToEto(this SKColorF color) => new(color.Red, color.Green, color.Blue, color.Alpha); private static readonly MethodInfo? m_FixScrollable = Assembly.GetEntryAssembly()?.GetType("CelesteStudio.WPF.Program")?.GetMethod("FixScrollable", BindingFlags.Public | BindingFlags.Static); public static Scrollable FixBorder(this Scrollable scrollable) {