diff --git a/CSharpMath.Apple/Drawing/AppleGraphicsContext.cs b/CSharpMath.Apple/Drawing/AppleGraphicsContext.cs index be541e786..a68076c7f 100644 --- a/CSharpMath.Apple/Drawing/AppleGraphicsContext.cs +++ b/CSharpMath.Apple/Drawing/AppleGraphicsContext.cs @@ -5,7 +5,7 @@ using CoreText; using CSharpMath.Display.Text; using CSharpMath.FrontEnd; -using CSharpMath.Structures; +using Color = CSharpMath.Structures.Color; using UIKit; using TFont = CSharpMath.Apple.AppleMathFont; using TGlyph = System.UInt16; diff --git a/CSharpMath.Editor/Extensions/DisplayEditingExtensions.Fraction.cs b/CSharpMath.Editor/Extensions/DisplayEditingExtensions.Fraction.cs index 43b735631..887eb511d 100644 --- a/CSharpMath.Editor/Extensions/DisplayEditingExtensions.Fraction.cs +++ b/CSharpMath.Editor/Extensions/DisplayEditingExtensions.Fraction.cs @@ -18,20 +18,24 @@ public static MathListIndex IndexForPoint(this FractionDisplay self.LinePosition + PixelDelta) return MathListIndex.IndexAtLocation(self.Range.Location, self.Numerator.IndexForPoint(context, point), MathListSubIndexType.Numerator); - else + else if (point.Y < self.LinePosition - PixelDelta) return MathListIndex.IndexAtLocation(self.Range.Location, self.Denominator.IndexForPoint(context, point), MathListSubIndexType.Denominator); + if (point.X > self.Position.X + self.Width / 2) + return MathListIndex.Level0Index(self.Range.End); + + return MathListIndex.Level0Index(self.Range.Location); } public static PointF? PointForIndex(this FractionDisplay self, TypesettingContext context, MathListIndex index) where TFont : IFont { if (index.SubIndexType != MathListSubIndexType.None) throw Arg("The subindex must be none to get the closest point for it.", nameof(index)); - // draw a caret after the fraction - return new PointF(self.DisplayBounds.Right, self.Position.Y); + if (index.AtomIndex == self.Range.End) + // draw a caret after the fraction + return new PointF(self.DisplayBounds.Right, self.Position.Y); + // draw a caret before the fraction + return new PointF(self.DisplayBounds.Left, self.Position.Y); } public static void HighlightCharacterAt(this FractionDisplay self, MathListIndex index, Color color) where TFont : IFont { diff --git a/CSharpMath.Editor/Extensions/DisplayEditingExtensions.List.cs b/CSharpMath.Editor/Extensions/DisplayEditingExtensions.List.cs index cc25cbb6d..385eb6e4d 100644 --- a/CSharpMath.Editor/Extensions/DisplayEditingExtensions.List.cs +++ b/CSharpMath.Editor/Extensions/DisplayEditingExtensions.List.cs @@ -2,7 +2,7 @@ namespace CSharpMath.Editor { using System; using System.Collections.Generic; using System.Drawing; - + using System.Linq; using Display; using Display.Text; using FrontEnd; @@ -18,10 +18,11 @@ public static MathListIndex IndexForPoint(this ListDisplay(this ListDisplay= self.Width + PixelDelta) + else if (translatedPoint.X >= self.Width + PixelDelta) { + // All the way to the right + if (closest != null) { + return MathListIndex.Level0Index(closest.Range.End); + } // All the way to the right - return MathListIndex.Level0Index(self.Range.End); - else + return self.Range.End < 0 ? null : MathListIndex.Level0Index(self.Range.End); + } else // It is within the ListDisplay but not within the X bounds of any sublist. Use the closest in that case. displayWithPoint = closest; break; case 1: displayWithPoint = xbounds[0]; + var rect = new RectangleF(displayWithPoint.Position, displayWithPoint.DisplayBounds.Size); if (translatedPoint.X >= self.Width - PixelDelta) //The point is close to the end. Only use the selected X bounds if the Y is within range. - if (translatedPoint.Y <= displayWithPoint.DisplayBounds.YMin() - PixelDelta) + if (translatedPoint.Y <= rect.YMin() - PixelDelta) //The point is less than the Y including the delta. Move the cursor to the end rather than in this atom. return MathListIndex.Level0Index(self.Range.End); break; @@ -88,7 +94,19 @@ public static MathListIndex IndexForPoint(this ListDisplay + d is ListDisplay ld && + ld.IndexInParent == index.AtomIndex - 1 + ); + if (scripted != null && mainPosition != null) { + position = new PointF(mainPosition.Value.X + scripted.Width, 0); + } else + position = mainPosition; + } break; default: // Recurse diff --git a/CSharpMath.Editor/Extensions/DisplayEditingExtensions.TextLine.cs b/CSharpMath.Editor/Extensions/DisplayEditingExtensions.TextLine.cs index 1c9eb7ab8..0e5bbeeab 100644 --- a/CSharpMath.Editor/Extensions/DisplayEditingExtensions.TextLine.cs +++ b/CSharpMath.Editor/Extensions/DisplayEditingExtensions.TextLine.cs @@ -10,20 +10,23 @@ namespace CSharpMath.Editor { public partial class DisplayEditingExtensions { public static int? GlyphIndexForXOffset(this AttributedGlyphRun line, TypesettingContext context, float offset) where TFont : IFont { - if (offset < 0) return 0; //Move cursor to index 0 + if (offset < 0) return 0; // Move cursor to index 0 + if (line.Placeholder) return 0; int i = 0; float x = 0; var advances = context.GlyphBoundsProvider.GetAdvancesForGlyphs(line.Font, line.Glyphs.AsForEach(), line.Length).Advances; foreach (var (advance, kernAfter) in advances.Zip(line.GlyphInfos.Select(g => g.KernAfterGlyph), ValueTuple.Create)) - if (x <= offset && offset < advance + x) - return i; - else { + if (x <= offset && offset < advance + x) { + if (Math.Abs(offset - x) < Math.Abs(advance + x - offset)) + return i; + return i + 1; + } else { x += advance + kernAfter; i++; - if (offset < x) //If the point is in the kern after this, then the index is the one after this + if (offset < x) // If the point is in the kern after this, then the index is the one after this return i; } - return null; + return i; } public static float XOffsetForGlyphIndex(this AttributedGlyphRun line, TypesettingContext context, int index) where TFont : IFont { @@ -67,19 +70,16 @@ public static int MathListIndexToStringIndex(this TextLineDisplay public static MathListIndex IndexForPoint(this TextLineDisplay self, TypesettingContext context, PointF point) where TFont : IFont { // Convert the point to the reference of the CTLine var relativePoint = new PointF(point.X - self.Position.X, point.Y - self.Position.Y); - var indices = self.Runs.Select(run => run.Run.GlyphIndexForXOffset(context, relativePoint.Plus(run.Position).X)).Where(x => x.HasValue); - if (indices.IsEmpty()) + var indices = self.Runs.Select(run => run.Run.GlyphIndexForXOffset(context, relativePoint.Plus(run.Position).X)).Where(x => x.HasValue).ToArray(); + if (indices.Length == 0) return null; var index = indices.Single().GetValueOrDefault(); - // The index returned is in UTF-16, translate to codepoint index. - // NSUInteger codePointIndex = stringIndexToCodePointIndex(self.attributedString.string, index); - // Convert the code point index to an index into the mathlist - var mlIndex = self.StringIndexToMathListIndex(index); + // index will be between 0 and _range.length inclusive - if (mlIndex < 0 || mlIndex > self.Range.Length) + if (index < 0 || index > self.Range.Length) throw new InvalidCodePathException($"Returned index out of range: {index}, range ({self.Range.Location}, {self.Range.Length})"); // translate to the current index - return MathListIndex.Level0Index(self.Range.Location + mlIndex); + return MathListIndex.Level0Index(self.Range.Location + index); } public static (TextRunDisplay run, int charIndex) GetRunAndCharIndexFromStringIndex(this TextLineDisplay self, int lineCharIndex) where TFont : IFont { diff --git a/CSharpMath.Editor/Extensions/DisplayEditingExtensions.cs b/CSharpMath.Editor/Extensions/DisplayEditingExtensions.cs index 475bce657..6eb21405d 100644 --- a/CSharpMath.Editor/Extensions/DisplayEditingExtensions.cs +++ b/CSharpMath.Editor/Extensions/DisplayEditingExtensions.cs @@ -67,7 +67,8 @@ public static float DistanceFromPointToRect(PointF point, RectangleF rect) { distance += point.Y - rect.YMax(); return distance; } - + public static float DistanceBetweenY(PointF p1, PointF p2) => (p1.Y - p2.Y) * (p1.Y - p2.Y); + /// /// Finds the index in the mathlist before which a new character should be inserted.Returns null if it cannot find the index. /// diff --git a/CSharpMath.Forms.Example/CSharpMath.Forms.Example/EditorPage.xaml b/CSharpMath.Forms.Example/CSharpMath.Forms.Example/EditorPage.xaml index 9876e0900..94fee8c2a 100644 --- a/CSharpMath.Forms.Example/CSharpMath.Forms.Example/EditorPage.xaml +++ b/CSharpMath.Forms.Example/CSharpMath.Forms.Example/EditorPage.xaml @@ -3,11 +3,4 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="CSharpMath.Forms.Example.EditorPage" Title="Editor"> - - - - \ No newline at end of file diff --git a/CSharpMath.Forms.Example/CSharpMath.Forms.Example/EditorPage.xaml.cs b/CSharpMath.Forms.Example/CSharpMath.Forms.Example/EditorPage.xaml.cs index 4de2426a6..7ab0c1b74 100644 --- a/CSharpMath.Forms.Example/CSharpMath.Forms.Example/EditorPage.xaml.cs +++ b/CSharpMath.Forms.Example/CSharpMath.Forms.Example/EditorPage.xaml.cs @@ -3,24 +3,43 @@ using System.Linq; using System.Text; using System.Threading.Tasks; - +using CSharpMath.Editor; +using SkiaSharp.Views.Forms; using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace CSharpMath.Forms.Example { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class EditorPage : ContentPage { + public EditorPage() { InitializeComponent(); - var view = new global::SkiaSharp.Views.Forms.SKCanvasView { WidthRequest = 320, HeightRequest = 225 }; + this.Content = new EditorView(); + } + + } + + public class EditorView : ContentView { + private MathKeyboard keyboard; + + public EditorView() { + keyboard = new MathKeyboard(); + + var view = new SKCanvasView { WidthRequest = 320, HeightRequest = 225, EnableTouchEvents = true }; + view.Touch += + (sender, e) => { + if (e.ActionType == SKTouchAction.Pressed) { + keyboard.Tap(new System.Drawing.PointF(e.Location.X, e.Location.Y)); + } + }; + var painter = new SkiaSharp.MathPainter { TextColor = global::SkiaSharp.SKColors.Black }; - var keyboard = new MathKeyboard(); keyboard.RedrawRequested += (_, __) => view.InvalidateSurface(); view.PaintSurface += (sender, e) => { e.Surface.Canvas.Clear(); SkiaSharp.MathPainter.DrawDisplay(painter, keyboard.Display, e.Surface.Canvas); - keyboard.DrawCaret(e.Surface.Canvas, Rendering.CaretShape.UpArrow); + keyboard.DrawCaret(e.Surface.Canvas, Rendering.CaretShape.IBeam); }; Content = new StackLayout { Children = { view, keyboard } }; } diff --git a/CSharpMath.Forms/MathKeyboard.xaml.cs b/CSharpMath.Forms/MathKeyboard.xaml.cs index a54da817c..cd6afb574 100644 --- a/CSharpMath.Forms/MathKeyboard.xaml.cs +++ b/CSharpMath.Forms/MathKeyboard.xaml.cs @@ -8,6 +8,7 @@ using Xamarin.Forms.Xaml; namespace CSharpMath.Forms { + using CSharpMath.Editor; using Rendering; [XamlCompilation(XamlCompilationOptions.Compile)] public partial class MathKeyboard : ContentView { @@ -15,6 +16,9 @@ public MathKeyboard() { InitializeComponent(); } + + public void Tap(System.Drawing.PointF point) => Keyboard.Tap(point); + public void DrawCaret(global::SkiaSharp.SKCanvas canvas, CaretShape shape = CaretShape.UpArrow) => Keyboard.DrawCaret(new SkiaSharp.SkiaCanvas(canvas, global::SkiaSharp.SKStrokeCap.Butt, false), shape); public event EventHandler RedrawRequested { @@ -30,7 +34,7 @@ public event EventHandler DismissPressed { remove => Keyboard.DismissPressed -= value; } - public IDisplay Display => Keyboard.Display; + public IDisplay Display => Keyboard.Display; private void SwitchTab(Grid tab) { tab.IsVisible = true; diff --git a/CSharpMath/CSharpMath.csproj b/CSharpMath/CSharpMath.csproj index 8c611b67f..67275a3d3 100644 --- a/CSharpMath/CSharpMath.csproj +++ b/CSharpMath/CSharpMath.csproj @@ -1,4 +1,4 @@ - + netstandard1.1 latest @@ -38,7 +38,7 @@ - + \ No newline at end of file