diff --git a/CSharpMath.CoreTests/MockTests.cs b/CSharpMath.CoreTests/MockTests.cs index da186bc6..74f44902 100644 --- a/CSharpMath.CoreTests/MockTests.cs +++ b/CSharpMath.CoreTests/MockTests.cs @@ -12,16 +12,17 @@ public void TestGlyphBoundsWithoutM() { var font = new TestFont(10); var provider = TestGlyphBoundsProvider.Instance; var glyphRun = new AttributedGlyphRun(hello, hello, font); + Assert.All(glyphRun.GlyphInfos, glyphInfo => Assert.Null(glyphInfo.Foreground)); var width = provider.GetTypographicWidth(font, glyphRun); Approximately.Equal(width, 25, 0.01); } - [Fact] public void TestGlyphBoundsWithM() { string america = "America"; var font = new TestFont(10); var provider = TestGlyphBoundsProvider.Instance; var glyphRun = new AttributedGlyphRun(america, america, font); + Assert.All(glyphRun.GlyphInfos, glyphInfo => Assert.Null(glyphInfo.Foreground)); var width = provider.GetTypographicWidth(font, glyphRun); Approximately.Equal(width, 40, 0.01); } diff --git a/CSharpMath.Editor.Tests/CaretTests.cs b/CSharpMath.Editor.Tests/CaretTests.cs index 03e7fb03..f61c7119 100644 --- a/CSharpMath.Editor.Tests/CaretTests.cs +++ b/CSharpMath.Editor.Tests/CaretTests.cs @@ -1,6 +1,6 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; +using CSharpMath.Atom; using CSharpMath.CoreTests.FrontEnd; using Xunit; @@ -32,18 +32,18 @@ public async Task Test() { var outer = Assert.IsType(Assert.Single(keyboard.MathList)); var inner = Assert.IsType(Assert.Single(outer.Superscript)); Assert.Equal(MathKeyboardCaretState.ShownThroughPlaceholder, keyboard.CaretState); - Assert.Equal("\u25A1", outer.Nucleus); - Assert.Equal("\u25A0", inner.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.RestingNucleus, outer.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.ActiveNucleus, inner.Nucleus); await Task.Delay((int)MathKeyboard.DefaultBlinkMilliseconds + CaretBlinks.MillisecondBuffer); Assert.Equal(MathKeyboardCaretState.TemporarilyHidden, keyboard.CaretState); - Assert.Equal("\u25A1", outer.Nucleus); - Assert.Equal("\u25A1", inner.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.RestingNucleus, outer.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.RestingNucleus, inner.Nucleus); await Task.Delay((int)MathKeyboard.DefaultBlinkMilliseconds + CaretBlinks.MillisecondBuffer); Assert.Equal(MathKeyboardCaretState.ShownThroughPlaceholder, keyboard.CaretState); - Assert.Equal("\u25A1", outer.Nucleus); - Assert.Equal("\u25A0", inner.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.RestingNucleus, outer.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.ActiveNucleus, inner.Nucleus); } } public class CaretMovesWithPlaceholder { @@ -58,20 +58,20 @@ public async Task Test() { var outer = Assert.IsType(Assert.Single(keyboard.MathList)); var inner = Assert.IsType(Assert.Single(outer.Subscript)); Assert.Equal(MathKeyboardCaretState.ShownThroughPlaceholder, keyboard.CaretState); - Assert.Equal("\u25A1", outer.Nucleus); - Assert.Equal("\u25A0", inner.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.RestingNucleus, outer.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.ActiveNucleus, inner.Nucleus); await Task.Delay((int)MathKeyboard.DefaultBlinkMilliseconds + CaretBlinks.MillisecondBuffer); Assert.Equal(MathKeyboardCaretState.TemporarilyHidden, keyboard.CaretState); keyboard.KeyPress(MathKeyboardInput.Left); Assert.Equal(MathKeyboardCaretState.ShownThroughPlaceholder, keyboard.CaretState); - Assert.Equal("\u25A0", outer.Nucleus); - Assert.Equal("\u25A1", inner.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.ActiveNucleus, outer.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.RestingNucleus, inner.Nucleus); Assert.Equal(MathKeyboardCaretState.ShownThroughPlaceholder, keyboard.CaretState); keyboard.KeyPress(MathKeyboardInput.Right); - Assert.Equal("\u25A1", outer.Nucleus); - Assert.Equal("\u25A0", inner.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.RestingNucleus, outer.Nucleus); + Assert.Equal(DefaultPlaceholderSettings.ActiveNucleus, inner.Nucleus); } } public class CaretStaysHidden { @@ -176,4 +176,101 @@ public async Task Test() { Assert.Equal(MathKeyboardCaretState.Shown, keyboard.CaretState); } } + public class DefaultPlaceholderSettings { + public const string ActiveNucleus = "โ– "; + public const string RestingNucleus = "โ–ก"; + public static readonly System.Drawing.Color? ActiveColor = null; + public static readonly System.Drawing.Color? RestingColor = null; + } + [CollectionDefinition(nameof(NonParallelPlaceholderTests), DisableParallelization = true)] + public class NonParallelPlaceholderTests { } + [Collection(nameof(NonParallelPlaceholderTests))] + public class DefaultPlaceholder { + [Fact] + public void LaTeXSettingsPlaceholderIsNewInstance() { + Assert.NotSame(LaTeXSettings.Placeholder, LaTeXSettings.Placeholder); + // Double check, also verify that its contents are 'fresh': + LaTeXSettings.Placeholder.Nucleus = "x"; + Assert.Equal(DefaultPlaceholderSettings.RestingNucleus, LaTeXSettings.Placeholder.Nucleus); + LaTeXSettings.Placeholder.Color = System.Drawing.Color.Green; + Assert.Equal(DefaultPlaceholderSettings.RestingColor, LaTeXSettings.Placeholder.Color); + } + [Fact] + public void DefaultPlaceholderAppearance() { + Assert.Null(LaTeXSettings.PlaceholderActiveColor); + Assert.Null(LaTeXSettings.PlaceholderRestingColor); + Assert.Equal(DefaultPlaceholderSettings.ActiveNucleus, LaTeXSettings.PlaceholderActiveNucleus); + Assert.Equal(DefaultPlaceholderSettings.RestingNucleus, LaTeXSettings.PlaceholderRestingNucleus); + Assert.Equal(LaTeXSettings.PlaceholderRestingNucleus, LaTeXSettings.Placeholder.Nucleus); + Assert.Equal(LaTeXSettings.PlaceholderRestingColor, LaTeXSettings.Placeholder.Color); + } + } + [Collection(nameof(NonParallelPlaceholderTests))] + public class CustomizablePlaceholder : IDisposable { + public CustomizablePlaceholder() { + LaTeXSettings.PlaceholderActiveNucleus = "๐Ÿ˜€"; + LaTeXSettings.PlaceholderRestingNucleus = "๐Ÿ˜"; + LaTeXSettings.PlaceholderActiveColor = System.Drawing.Color.Green; + LaTeXSettings.PlaceholderRestingColor = System.Drawing.Color.Blue; + } + public void Dispose() { + LaTeXSettings.PlaceholderActiveNucleus = DefaultPlaceholderSettings.ActiveNucleus; + LaTeXSettings.PlaceholderRestingNucleus = DefaultPlaceholderSettings.RestingNucleus; + LaTeXSettings.PlaceholderActiveColor = DefaultPlaceholderSettings.ActiveColor; + LaTeXSettings.PlaceholderRestingColor = DefaultPlaceholderSettings.RestingColor; + } + [Fact] + public async Task CustomizedPlaceholderBlinks() { + var keyboard = new MathKeyboard(TestTypesettingContexts.Instance, new TestFont()) { + CaretState = MathKeyboardCaretState.Shown + }; + Assert.Equal(MathKeyboardCaretState.Shown, keyboard.CaretState); + + keyboard.KeyPress(MathKeyboardInput.Subscript); + var outer = Assert.IsType(Assert.Single(keyboard.MathList)); + var inner = Assert.IsType(Assert.Single(outer.Subscript)); + Assert.Equal(MathKeyboardCaretState.ShownThroughPlaceholder, keyboard.CaretState); + Assert.Equal("๐Ÿ˜", outer.Nucleus); + Assert.Equal(System.Drawing.Color.Blue, outer.Color); + Assert.Equal("๐Ÿ˜€", inner.Nucleus); + Assert.Equal(System.Drawing.Color.Green, inner.Color); + + await Task.Delay((int)MathKeyboard.DefaultBlinkMilliseconds + CaretBlinks.MillisecondBuffer); + Assert.Equal(MathKeyboardCaretState.TemporarilyHidden, keyboard.CaretState); + Assert.Equal("๐Ÿ˜", outer.Nucleus); + Assert.Equal(System.Drawing.Color.Blue, outer.Color); + Assert.Equal("๐Ÿ˜", inner.Nucleus); + Assert.Equal(System.Drawing.Color.Blue, inner.Color); + + await Task.Delay((int)MathKeyboard.DefaultBlinkMilliseconds + CaretBlinks.MillisecondBuffer); + Assert.Equal(MathKeyboardCaretState.ShownThroughPlaceholder, keyboard.CaretState); + Assert.Equal("๐Ÿ˜", outer.Nucleus); + Assert.Equal(System.Drawing.Color.Blue, outer.Color); + Assert.Equal("๐Ÿ˜€", inner.Nucleus); + Assert.Equal(System.Drawing.Color.Green, inner.Color); + } + [Fact] + public void AllCustomizablePlaceholderPropertiesAreResetOnCaretVisible() { + var keyboard = new MathKeyboard(TestTypesettingContexts.Instance, new TestFont()) { + CaretState = MathKeyboardCaretState.Shown + }; + Assert.Equal(MathKeyboardCaretState.Shown, keyboard.CaretState); + keyboard.KeyPress(MathKeyboardInput.Subscript); + var outer = Assert.IsType(Assert.Single(keyboard.MathList)); + var inner = Assert.IsType(Assert.Single(outer.Subscript)); + Assert.Equal(MathKeyboardCaretState.ShownThroughPlaceholder, keyboard.CaretState); + + keyboard.InsertionIndex = MathListIndex.Level0Index(keyboard.MathList.Count); + Assert.Equal(MathKeyboardCaretState.Shown, keyboard.CaretState); + Assert.Equal(LaTeXSettings.PlaceholderRestingNucleus, outer.Nucleus); + Assert.Equal(LaTeXSettings.PlaceholderRestingColor, outer.Color); + Assert.Equal(LaTeXSettings.PlaceholderRestingNucleus, inner.Nucleus); + Assert.Equal(LaTeXSettings.PlaceholderRestingColor, inner.Color); + } + [Fact] + public void CustomizedPlaceholderGetter() { + Assert.Equal("๐Ÿ˜", LaTeXSettings.Placeholder.Nucleus); + Assert.Equal(System.Drawing.Color.Blue, LaTeXSettings.Placeholder.Color); + } + } } diff --git a/CSharpMath.Editor/MathKeyboard.cs b/CSharpMath.Editor/MathKeyboard.cs index 104aaec8..f95a0904 100644 --- a/CSharpMath.Editor/MathKeyboard.cs +++ b/CSharpMath.Editor/MathKeyboard.cs @@ -44,8 +44,9 @@ static void ResetPlaceholders(MathList mathList) { ResetPlaceholders(mathAtom.Superscript); ResetPlaceholders(mathAtom.Subscript); switch (mathAtom) { - case Atoms.Placeholder _: - mathAtom.Nucleus = "\u25A1"; + case Atoms.Placeholder placeholder: + placeholder.Color = LaTeXSettings.PlaceholderRestingColor; + placeholder.Nucleus = LaTeXSettings.PlaceholderRestingNucleus; break; case IMathListContainer container: foreach (var list in container.InnerLists) @@ -62,10 +63,10 @@ public MathKeyboardCaretState CaretState { blinkTimer.Start(); if (value != MathKeyboardCaretState.Hidden && MathList.AtomAt(_insertionIndex) is Atoms.Placeholder placeholder) - (placeholder.Nucleus, _caretState) = + (placeholder.Nucleus, placeholder.Color, _caretState) = value == MathKeyboardCaretState.TemporarilyHidden - ? ("\u25A1", MathKeyboardCaretState.TemporarilyHidden) - : ("\u25A0", MathKeyboardCaretState.ShownThroughPlaceholder); + ? (LaTeXSettings.PlaceholderRestingNucleus, LaTeXSettings.PlaceholderRestingColor, MathKeyboardCaretState.TemporarilyHidden) + : (LaTeXSettings.PlaceholderActiveNucleus, LaTeXSettings.PlaceholderActiveColor, MathKeyboardCaretState.ShownThroughPlaceholder); else _caretState = value; RecreateDisplayFromMathList(); RedrawRequested?.Invoke(this, EventArgs.Empty); diff --git a/CSharpMath/Atom/Atoms/Placeholder.cs b/CSharpMath/Atom/Atoms/Placeholder.cs index 7c27cd24..2e381ff7 100644 --- a/CSharpMath/Atom/Atoms/Placeholder.cs +++ b/CSharpMath/Atom/Atoms/Placeholder.cs @@ -1,9 +1,11 @@ +using System.Drawing; namespace CSharpMath.Atom.Atoms { /// A placeholder for future input public sealed class Placeholder : MathAtom { - public Placeholder(string nucleus) : base(nucleus) { } + public Color? Color { get; set; } + public Placeholder(string nucleus, Color? color) : base(nucleus) => Color = color; public override bool ScriptsAllowed => true; public new Placeholder Clone(bool finalize) => (Placeholder)base.Clone(finalize); - protected override MathAtom CloneInside(bool finalize) => new Placeholder(Nucleus); + protected override MathAtom CloneInside(bool finalize) => new Placeholder(Nucleus, Color); } } \ No newline at end of file diff --git a/CSharpMath/Atom/LaTeXSettings.cs b/CSharpMath/Atom/LaTeXSettings.cs index 6c1aeb84..dc5fe09f 100644 --- a/CSharpMath/Atom/LaTeXSettings.cs +++ b/CSharpMath/Atom/LaTeXSettings.cs @@ -320,7 +320,11 @@ public static class LaTeXSettings { }; public static MathAtom Times => new BinaryOperator("ร—"); public static MathAtom Divide => new BinaryOperator("รท"); - public static MathAtom Placeholder => new Placeholder("\u25A1"); + public static Color? PlaceholderRestingColor { get; set; } + public static Color? PlaceholderActiveColor { get; set; } + public static string PlaceholderActiveNucleus { get; set; } = "\u25A0"; + public static string PlaceholderRestingNucleus { get; set; } = "\u25A1"; + public static Placeholder Placeholder => new Placeholder(PlaceholderRestingNucleus, PlaceholderRestingColor); public static MathList PlaceholderList => new MathList { Placeholder }; public static AliasBiDictionary FontStyles { get; } = diff --git a/CSharpMath/Display/AttributedGlyphRun.cs b/CSharpMath/Display/AttributedGlyphRun.cs index 575e96a8..c5914753 100644 --- a/CSharpMath/Display/AttributedGlyphRun.cs +++ b/CSharpMath/Display/AttributedGlyphRun.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; @@ -7,9 +8,9 @@ namespace CSharpMath.Display { /// over the whole string. We use KernedGlyph objects instead of Glyphs to /// allow us to set kern on a per-glyph basis. public class AttributedGlyphRun where TFont : FrontEnd.IFont { - public AttributedGlyphRun(string text, IEnumerable glyphs, TFont font, bool isPlaceHolder = false) { + public AttributedGlyphRun(string text, IEnumerable glyphs, TFont font, bool isPlaceHolder = false, Color? color = null) { Text = new StringBuilder(text); - GlyphInfos = glyphs.Select(g => new GlyphInfo(g)).ToList(); + GlyphInfos = glyphs.Select(g => new GlyphInfo(g) { Foreground = color }).ToList(); Font = font; Placeholder = isPlaceHolder; } diff --git a/CSharpMath/Display/Typesetter.cs b/CSharpMath/Display/Typesetter.cs index a222d170..d9ee595c 100644 --- a/CSharpMath/Display/Typesetter.cs +++ b/CSharpMath/Display/Typesetter.cs @@ -337,7 +337,7 @@ private void CreateDisplayAtoms(List preprocessedAtoms) { var nucleusText = atom.Nucleus; var glyphs = _context.GlyphFinder.FindGlyphs(_font, nucleusText); var current = new AttributedGlyphRun( - nucleusText, glyphs, _font, atom is Placeholder); + nucleusText, glyphs, _font, atom is Placeholder, (atom as Placeholder)?.Color); _currentLine.AppendGlyphRun(current); if (_currentLineIndexRange.Location == Range.UndefinedInt) _currentLineIndexRange = atom.IndexRange;