Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2892 from VsVim/dev/nosami/relative-line-numbers
[Mac] Add first draft of relative numbers
- Loading branch information
Showing
12 changed files
with
941 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Linq; | ||
using System.Windows; | ||
using System.Windows.Media; | ||
|
||
using AppKit; | ||
using CoreAnimation; | ||
using CoreGraphics; | ||
using CoreText; | ||
using Foundation; | ||
|
||
namespace Vim.UI.Cocoa.Implementation.RelativeLineNumbers | ||
{ | ||
public static class CocoaExtensions | ||
{ | ||
public static double WidthIncludingTrailingWhitespace(this CTLine line) | ||
=> line.GetBounds(0).Width; | ||
|
||
//Before "optimising" this to `new CGColor(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);` | ||
//notice that doesn't handle colorspace correctly | ||
public static CGColor AsCGColor(this Color color) | ||
=> NSColor.FromRgba(color.R, color.G, color.B, color.A).CGColor; | ||
|
||
public static bool IsDarkColor(this CGColor color) | ||
{ | ||
double red; | ||
double green; | ||
double blue; | ||
|
||
var components = color?.Components; | ||
|
||
if (components == null || components.Length == 0) | ||
return false; | ||
|
||
if (components.Length >= 3) | ||
{ | ||
red = components[0]; | ||
green = components[1]; | ||
blue = components[2]; | ||
} | ||
else | ||
{ | ||
red = green = blue = components[0]; | ||
} | ||
|
||
// https://www.w3.org/WAI/ER/WD-AERT/#color-contrast | ||
var brightness = (red * 255 * 299 + green * 255 * 587 + blue * 255 * 114) / 1000; | ||
return brightness <= 155; | ||
} | ||
|
||
public static bool IsMouseOver(this NSView view) | ||
{ | ||
var mousePoint = NSEvent.CurrentMouseLocation; | ||
return IsMouseOver(view, mousePoint); | ||
} | ||
|
||
public static bool IsMouseOver(this NSView view, CGPoint mousePoint) | ||
{ | ||
var window = view.Window; | ||
if (window == null) | ||
{ | ||
return false; | ||
} | ||
var viewScreenRect = window.ConvertRectToScreen( | ||
view.ConvertRectToView(view.Frame, null)); | ||
return viewScreenRect.Contains(mousePoint); | ||
} | ||
|
||
public static Rect AsRect(this CGRect rect) | ||
{ | ||
return new Rect(rect.X, rect.Y, rect.Width, rect.Height); | ||
} | ||
|
||
public static CGPoint AsCGPoint(this Point point) | ||
{ | ||
return new CGPoint(point.X, point.Y); | ||
} | ||
|
||
public static Point PointToScreen(this NSView view, Point point) | ||
{ | ||
var p = view.ConvertPointToView(new CGPoint(point.X, point.Y), null); | ||
if (view.Window == null) | ||
return new Point(p.X, p.Y); | ||
p = view.Window.ConvertPointToScreen(p); | ||
return new Point(p.X, p.Y); | ||
} | ||
|
||
public static CGPoint PointToScreen(this NSView view, CGPoint point) | ||
{ | ||
var p = view.ConvertPointToView(point, null); | ||
if (view.Window == null) | ||
return p; | ||
p = view.Window.ConvertPointToScreen(p); | ||
return p; | ||
} | ||
|
||
private static readonly NSDictionary disableAnimations = new NSDictionary( | ||
"actions", NSNull.Null, | ||
"contents", NSNull.Null, | ||
"hidden", NSNull.Null, | ||
"onLayout", NSNull.Null, | ||
"onOrderIn", NSNull.Null, | ||
"onOrderOut", NSNull.Null, | ||
"position", NSNull.Null, | ||
"sublayers", NSNull.Null, | ||
"transform", NSNull.Null, | ||
"bounds", NSNull.Null); | ||
|
||
public static void DisableImplicitAnimations(this CALayer layer) | ||
=> layer.Actions = disableAnimations; | ||
} | ||
} | ||
|
||
namespace CoreGraphics | ||
{ | ||
internal static class CoreGraphicsExtensions | ||
{ | ||
public static CGSize InflateToIntegral(this CGSize size) | ||
=> new CGSize(NMath.Ceiling(size.Width), NMath.Ceiling(size.Height)); | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
Src/VimMac/RelativeLineNumbers/CocoaLineNumberMarginDrawingVisual.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
using System; | ||
using System.Globalization; | ||
|
||
using AppKit; | ||
using CoreAnimation; | ||
using CoreGraphics; | ||
using CoreText; | ||
using Foundation; | ||
using Vim.UI.Wpf.Implementation.RelativeLineNumbers; | ||
|
||
namespace Vim.UI.Cocoa.Implementation.RelativeLineNumbers | ||
{ | ||
internal sealed class CocoaLineNumberMarginDrawingVisual : CALayer, ICALayerDelegate | ||
{ | ||
private NSStringAttributes stringAttributes; | ||
private CGRect lineBounds; | ||
private nfloat lineAscent; | ||
private CTLine ctLine; | ||
|
||
public int LineNumber { get; private set; } | ||
public int DisplayNumber { get; private set; } | ||
|
||
public bool InUse | ||
{ | ||
get => !Hidden; | ||
set => Hidden = !value; | ||
} | ||
|
||
public CocoaLineNumberMarginDrawingVisual() | ||
{ | ||
Delegate = this; | ||
NeedsDisplayOnBoundsChange = true; | ||
this.DisableImplicitAnimations(); | ||
} | ||
|
||
internal void Update( | ||
NSStringAttributes stringAttributes, | ||
Line line, | ||
nfloat lineWidth) | ||
{ | ||
// NOTE: keep this in sync with CocoaRenderedLineVisual regarding any font | ||
// metric handling, transforms, etc. Ensure that line numbers are always | ||
// exactly aligned with the actual editor text lines. Test with Fluent | ||
// Calibri and many other fonts at various sizes. | ||
|
||
if (DisplayNumber != line.DisplayNumber || this.stringAttributes != stringAttributes) | ||
{ | ||
DisplayNumber = line.DisplayNumber; | ||
this.stringAttributes = stringAttributes; | ||
|
||
ctLine?.Dispose(); | ||
ctLine = new CTLine(new NSAttributedString( | ||
line.DisplayNumber.ToString(CultureInfo.CurrentUICulture.NumberFormat), | ||
stringAttributes)); | ||
|
||
lineBounds = ctLine.GetBounds(0); | ||
ctLine.GetTypographicBounds(out lineAscent, out _, out _); | ||
|
||
SetNeedsDisplay(); | ||
} | ||
|
||
AffineTransform = new CGAffineTransform( | ||
1, 0, | ||
0, 1, | ||
0, (nfloat)line.TextTop); | ||
|
||
var transformRect = AffineTransform.TransformRect(new CGRect( | ||
line.IsCaretLine ? 0 : lineWidth - lineBounds.Width, // right justify | ||
line.Baseline - lineAscent, | ||
lineBounds.Width, | ||
lineBounds.Height)); | ||
|
||
Frame = new CGRect( | ||
NMath.Floor(transformRect.X), | ||
NMath.Floor(transformRect.Y), | ||
NMath.Ceiling(transformRect.Width), | ||
NMath.Ceiling(transformRect.Height)); | ||
} | ||
|
||
[Export("drawLayer:inContext:")] | ||
private void Draw(CALayer _, CGContext context) | ||
{ | ||
if (ctLine == null) | ||
return; | ||
|
||
CocoaTextManager.ConfigureContext(context); | ||
|
||
context.TextPosition = new CGPoint(0, -lineAscent); | ||
context.ScaleCTM(1, -1); | ||
|
||
ctLine.Draw(context); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
using System; | ||
|
||
using CoreGraphics; | ||
using Foundation; | ||
|
||
namespace Vim.UI.Cocoa.Implementation.RelativeLineNumbers | ||
{ | ||
internal static class CocoaTextManager | ||
{ | ||
private static readonly NSString AppleFontSmoothing = new NSString(nameof(AppleFontSmoothing)); | ||
private static readonly IDisposable smoothingObserver; | ||
private static int smoothing; | ||
|
||
public static event EventHandler DefaultsChanged; | ||
|
||
static CocoaTextManager() | ||
{ | ||
UpdateSmoothing(NSUserDefaults.StandardUserDefaults.ValueForKey(AppleFontSmoothing)); | ||
smoothingObserver = NSUserDefaults.StandardUserDefaults.AddObserver( | ||
AppleFontSmoothing, | ||
NSKeyValueObservingOptions.New, | ||
change => UpdateSmoothing(change?.NewValue)); | ||
} | ||
|
||
private static void UpdateSmoothing(NSObject value) | ||
{ | ||
var newSmoothing = value is NSNumber number | ||
? number.Int32Value | ||
: -1; | ||
|
||
if (newSmoothing == smoothing) | ||
return; | ||
|
||
smoothing = newSmoothing; | ||
|
||
DefaultsChanged?.Invoke(null, EventArgs.Empty); | ||
} | ||
|
||
public static void ConfigureContext(CGContext context) | ||
{ | ||
if (context == null) | ||
return; | ||
|
||
context.SetShouldAntialias(true); | ||
context.SetShouldSubpixelPositionFonts(true); | ||
|
||
if (MacRuntimeEnvironment.MojaveOrNewer) | ||
{ | ||
context.SetAllowsFontSmoothing(smoothing < 0); | ||
} | ||
else | ||
{ | ||
// NOTE: we cannot do proper subpixel AA because the layer/context | ||
// needs a background color which is not available in the text layer. | ||
// Selections and highlights are separate layers in the editor. If | ||
// we had reliable background colors, we could enable subpixel AA | ||
// "smoothing" by setting this to true and ensuring the context | ||
// had a background color (by way of the target layer or calling | ||
// the private CGContextSetFontSmoothingBackgroundColor. | ||
context.SetShouldSmoothFonts(false); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System.Diagnostics; | ||
|
||
namespace Vim.UI.Wpf.Implementation.RelativeLineNumbers | ||
{ | ||
[DebuggerDisplay("{LineNumber} {DisplayNumber} {Baseline}")] | ||
internal readonly struct Line | ||
{ | ||
public int LineNumber { get; } | ||
|
||
public int DisplayNumber { get; } | ||
|
||
public double Baseline { get; } | ||
|
||
public double TextTop { get; } | ||
|
||
public bool IsCaretLine { get; } | ||
|
||
public Line(int lineNumber, int displayNumber, double verticalBaseline, double textTop, bool isCaretLine) | ||
{ | ||
LineNumber = lineNumber; | ||
DisplayNumber = displayNumber; | ||
Baseline = verticalBaseline; | ||
TextTop = textTop; | ||
IsCaretLine = isCaretLine; | ||
} | ||
} | ||
} |
Oops, something went wrong.