示例#1
0
        private void AlignCaretToNearestCluster(bool isTrailingHit, bool skipZeroWidth)
        {
            // Uses hit-testing to align the current caret position to a whole cluster,
            // rather than residing in the middle of a base character + diacritic,
            // surrogate pair, or character + UVS.

            // Align the caret to the nearest whole cluster.
            HitTestMetrics hitTestMetrics = TextLayout.HitTestTextPosition(m_caretPosition, false);

            // The caret position itself is always the leading edge.
            // An additional offset indicates a trailing edge when non-zero.
            // This offset comes from the number of code-units in the
            // selected cluster or surrogate pair.
            m_caretPosition       = hitTestMetrics.TextPosition;
            m_caretPositionOffset = (isTrailingHit) ? hitTestMetrics.Length : 0;

            // For invisible, zero-width characters (like line breaks
            // and formatting characters), force leading edge of the
            // next position.
            if (skipZeroWidth && hitTestMetrics.Width == 0)
            {
                m_caretPosition      += m_caretPositionOffset;
                m_caretPositionOffset = 0;
            }
        }
示例#2
0
        public void Should_Get_CharacterHit_From_Distance_RTL()
        {
            using (Start())
            {
                var text = "أَبْجَدِيَّة عَرَبِيَّة";

                var layout = new TextLayout(
                    text,
                    Typeface.Default,
                    12,
                    Brushes.Black);

                var textLine = layout.TextLines[0];

                var firstRun = (ShapedTextCharacters)textLine.TextRuns[0];

                var firstCluster = firstRun.ShapedBuffer.GlyphClusters[0];

                var characterHit = textLine.GetCharacterHitFromDistance(0);

                Assert.Equal(firstCluster, characterHit.FirstCharacterIndex);

                Assert.Equal(text.Length, characterHit.FirstCharacterIndex + characterHit.TrailingLength);

                var distance = textLine.GetDistanceFromCharacterHit(characterHit);

                Assert.Equal(0, distance);

                distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(characterHit.FirstCharacterIndex));

                var firstAdvance = firstRun.ShapedBuffer.GlyphAdvances[0];

                Assert.Equal(firstAdvance, distance, 5);

                var rect = layout.HitTestTextPosition(22);

                Assert.Equal(firstAdvance, rect.Left, 5);

                rect = layout.HitTestTextPosition(23);

                Assert.Equal(0, rect.Left, 5);
            }
        }
示例#3
0
 public Point GetCaretPosition(FormattedText text, int caretIndex, Size constraint)
 {
     using (TextLayout layout = GetTextLayout(this.factory, text, constraint))
     {
         float x;
         float y;
         layout.HitTestTextPosition(caretIndex, false, out x, out y);
         return(new Point(x, y));
     }
 }
示例#4
0
        /// <summary>
        /// Renders the <see cref="AccessText"/> to a drawing context.
        /// </summary>
        /// <param name="context">The drawing context.</param>
        public override void Render(DrawingContext context)
        {
            base.Render(context);

            int underscore = Text?.IndexOf('_') ?? -1;

            if (underscore != -1 && ShowAccessKey)
            {
                var rect   = TextLayout.HitTestTextPosition(underscore);
                var offset = new Vector(0, -1.5);
                context.DrawLine(
                    new Pen(Foreground, 1),
                    rect.BottomLeft + offset,
                    rect.BottomRight + offset);
            }
        }
        public void Evaluate(int SpreadMax)
        {
            if (!FLayout.IsConnected)
            {
                this.FPosition.SliceCount = 0;
                return;
            }

            if (this.FLayout.IsChanged || this.FIndex.IsChanged || this.FTrailing.IsChanged)
            {
                this.FPosition.SliceCount = SpreadMax;

                for (int i = 0; i < SpreadMax; i++)
                {
                    TextLayout layout = this.FLayout[i];
                    float      x, y;
                    var        result = layout.HitTestTextPosition(this.FIndex[i], this.FTrailing[i], out x, out y);
                    this.FPosition[i] = new Vector2(x, y);
                }
            }
        }
示例#6
0
        /// <summary>
        /// Obtains the current caret position (in untransformed space)</summary>
        /// <returns>Current caret position rectangle (in untransformed space)</returns>
        public RectangleF GetCaretRect()
        {
            if (TextLayout == null)
            {
                return(new RectangleF());
            }

            var   caretMetrics = TextLayout.HitTestTextPosition(m_caretPosition, m_caretPositionOffset > 0);
            float caretX       = caretMetrics.Point.X;
            float caretY       = caretMetrics.Point.Y;

            UpdateSelectionRange();
            // If a selection exists, draw the caret using the
            // line size rather than the font size.
            if (SelectionLength > 0)
            {
                var lineMetrics = TextLayout.HitTestTextRange(m_caretPosition, 0, 0, 0);
                caretY = lineMetrics[0].Top;
            }

            const int caretThickness = 1;

            return(new RectangleF(caretX - caretThickness / 2.0f, caretY, caretThickness, caretMetrics.Height));
        }
示例#7
0
        /// <summary>
        /// Sets text selection. This may possibly only move the caret, not selecting characters.</summary>
        /// <param name="moveMode">Text selection mode</param>
        /// <param name="advance">Number of characters to advance or start selection</param>
        /// <param name="extendSelection">Whether to extend current selection to additional selection</param>
        /// <param name="updateCaretFormat">Whether to update caret format based on selection</param>
        /// <returns>True iff caret changed position as result of selection</returns>
        public bool SetSelection(SelectionMode moveMode, int advance, bool extendSelection, bool updateCaretFormat)
        {
            // Moves the caret relatively or absolutely, optionally extending the
            // selection range (for example, when shift is held).

            int line                = int.MaxValue; // current line number, needed by a few modes
            int absolutePosition    = m_caretPosition + m_caretPositionOffset;
            int oldAbsolutePosition = absolutePosition;
            int oldCaretAnchor      = m_caretAnchor;

            switch (moveMode)
            {
            case SelectionMode.Left:
                m_caretPosition += m_caretPositionOffset;
                if (m_caretPosition > 0)
                {
                    --m_caretPosition;
                    AlignCaretToNearestCluster(false, true);

                    // special check for CR/LF pair
                    absolutePosition = m_caretPosition + m_caretPositionOffset;
                    if (absolutePosition >= 1 &&
                        absolutePosition < TextLayout.Text.Length &&
                        TextLayout.Text[absolutePosition - 1] == '\r' &&
                        TextLayout.Text[absolutePosition] == '\n')
                    {
                        m_caretPosition = absolutePosition - 1;
                        AlignCaretToNearestCluster(false, true);
                    }
                }
                break;

            case SelectionMode.Right:
                m_caretPosition = absolutePosition;
                AlignCaretToNearestCluster(true, true);

                // special check for CR/LF pair
                absolutePosition = m_caretPosition + m_caretPositionOffset;
                if (absolutePosition >= 1 &&
                    absolutePosition < TextLayout.Text.Length &&
                    TextLayout.Text[absolutePosition - 1] == '\r' &&
                    TextLayout.Text[absolutePosition] == '\n')
                {
                    m_caretPosition = absolutePosition + 1;
                    AlignCaretToNearestCluster(false, true);
                }
                break;

            case SelectionMode.LeftChar:
                m_caretPosition       = absolutePosition;
                m_caretPosition      -= Math.Min(advance, absolutePosition);
                m_caretPositionOffset = 0;
                break;

            case SelectionMode.RightChar:
                m_caretPosition       = absolutePosition + advance;
                m_caretPositionOffset = 0;
                {
                    // Use hit-testing to limit text position.
                    HitTestMetrics hitTestMetrics = TextLayout.HitTestTextPosition(
                        m_caretPosition,
                        false);
                    m_caretPosition = Math.Min(m_caretPosition, hitTestMetrics.TextPosition + hitTestMetrics.Length);
                }
                break;

            case SelectionMode.Up:
            case SelectionMode.Down:
            {
                // Retrieve the line metrics to figure out what line we are on.
                var lineMetrics = TextLayout.GetLineMetrics();
                int linePosition;
                GetLineFromPosition(lineMetrics, m_caretPosition, out line, out linePosition);

                // Move up a line or down
                if (moveMode == SelectionMode.Up)
                {
                    if (line <= 0)
                    {
                        break;         // already top line
                    }
                    line--;
                    linePosition -= lineMetrics[line].Length;
                    if (line <= TopLine)
                    {
                        TopLine = TopLine - 1 >= 0 ? TopLine - 1 : 0;         // scroll down a line of text
                    }
                }
                else
                {
                    linePosition += lineMetrics[line].Length;
                    line++;
                    if (line >= lineMetrics.Length)
                    {
                        break;         // already bottom line
                    }
                    // scroll up a line of text
                    TopLine = TopLine + 1;
                }

                // To move up or down, we need three hit-testing calls to determine:
                // 1. The x of where we currently are.
                // 2. The y of the new line.
                // 3. New text position from the determined x and y.
                // This is because the characters are variable size.


                float caretX, caretY;
                // Get x of current text position
                var hitTestMetrics = TextLayout.HitTestTextPosition(
                    m_caretPosition,
                    m_caretPositionOffset > 0        // trailing if nonzero, else leading edge
                    );
                caretX = hitTestMetrics.Point.X;

                // Get y of new position
                hitTestMetrics = TextLayout.HitTestTextPosition(
                    linePosition,
                    false       // leading edge
                    );
                caretY = hitTestMetrics.Point.Y;

                // Now get text position of new x,y
                hitTestMetrics = TextLayout.HitTestPoint(caretX, caretY);


                m_caretPosition       = hitTestMetrics.TextPosition;
                m_caretPositionOffset = hitTestMetrics.IsTrailingHit ? (hitTestMetrics.Length > 0) ? 1 : 0 : 0;
            }
            break;

            case SelectionMode.LeftWord:
            case SelectionMode.RightWord:
            {
                // To navigate by whole words, we look for the canWrapLineAfter
                // flag in the cluster metrics.

                // Now we actually read them.
                var clusterMetrics = TextLayout.GetClusterMetrics();
                if (clusterMetrics.Length == 0)
                {
                    break;
                }

                m_caretPosition = absolutePosition;

                int clusterPosition  = 0;
                int oldCaretPosition = m_caretPosition;

                if (moveMode == SelectionMode.LeftWord)
                {
                    // Read through the clusters, keeping track of the farthest valid
                    // stopping point just before the old position.
                    m_caretPosition       = 0;
                    m_caretPositionOffset = 0;         // leading edge
                    for (int cluster = 0; cluster < clusterMetrics.Length; ++cluster)
                    {
                        clusterPosition += clusterMetrics[cluster].Length;
                        if (clusterMetrics[cluster].CanWrapLineAfter)
                        {
                            if (clusterPosition >= oldCaretPosition)
                            {
                                break;
                            }

                            // Update in case we pass this point next loop.
                            m_caretPosition = clusterPosition;
                        }
                    }
                }
                else         // SetSelectionModeRightWord
                {
                    // Read through the clusters, looking for the first stopping point
                    // after the old position.
                    for (int cluster = 0; cluster < clusterMetrics.Length; ++cluster)
                    {
                        int clusterLength = clusterMetrics[cluster].Length;
                        m_caretPosition       = clusterPosition;
                        m_caretPositionOffset = clusterLength;         // trailing edge
                        if (clusterPosition >= oldCaretPosition && clusterMetrics[cluster].CanWrapLineAfter)
                        {
                            break;         // first stopping point after old position.
                        }
                        clusterPosition += clusterLength;
                    }
                }
            }
            break;

            case SelectionMode.SingleWord:
            {
                var clusterMetrics = TextLayout.GetClusterMetrics();
                if (clusterMetrics.Length == 0)
                {
                    break;
                }

                // Left of word
                m_caretPosition = absolutePosition;

                int clusterPosition  = 0;
                int oldCaretPosition = m_caretPosition;

                // Read through the clusters, keeping track of the farthest valid
                // stopping point just before the old position.
                m_caretPosition       = 0;
                m_caretPositionOffset = 0;         // leading edge
                for (int cluster = 0; cluster < clusterMetrics.Length; ++cluster)
                {
                    clusterPosition += clusterMetrics[cluster].Length;
                    if (clusterMetrics[cluster].CanWrapLineAfter)
                    {
                        if (clusterPosition >= oldCaretPosition)
                        {
                            break;
                        }

                        // Update in case we pass this point next loop.
                        m_caretPosition = clusterPosition;
                    }
                }

                int leftOfWord = m_caretPosition;


                // Right of word
                // Read through the clusters, looking for the first stopping point
                // after the old position.
                for (int cluster = 0; cluster < clusterMetrics[cluster].Length; ++cluster)
                {
                    int clusterLength = clusterMetrics[cluster].Length;
                    m_caretPosition       = clusterPosition;
                    m_caretPositionOffset = clusterLength;         // trailing edge
                    if (clusterPosition >= oldCaretPosition && clusterMetrics[cluster].CanWrapLineAfter)
                    {
                        break;         // first stopping point after old position.
                    }
                    clusterPosition += clusterLength;
                }

                int rightOfWord = m_caretPosition - 1;
                m_caretPositionOffset = 0;
                m_caretAnchor         = leftOfWord;

                //while (rightOfWord > leftOfWord)
                //{
                //    char c = TextLayout.Text[rightOfWord];
                //    if (!(char.IsWhiteSpace(c) || char.IsPunctuation(c)))
                //        break;
                //    --rightOfWord;
                //}
                m_caretPosition = rightOfWord;
            }
            break;

            case SelectionMode.Home:
            case SelectionMode.End:
            {
                // Retrieve the line metrics to know first and last position
                // on the current line.
                var lineMetrics = TextLayout.GetLineMetrics();
                int linePosition;
                GetLineFromPosition(lineMetrics, m_caretPosition, out line, out linePosition);
                m_caretPosition = linePosition;

                m_caretPositionOffset = 0;
                if (moveMode == SelectionMode.End)
                {
                    // Place the caret at the last character on the line,
                    // excluding line breaks. In the case of wrapped lines,
                    // newlineLength will be 0.
                    int lineLength = lineMetrics[line].Length - lineMetrics[line].NewlineLength;
                    m_caretPositionOffset = Math.Min(lineLength, 1);
                    m_caretPosition      += lineLength - m_caretPositionOffset;
                    AlignCaretToNearestCluster(true, false);
                }
            }
            break;

            case SelectionMode.First:
                m_caretPosition       = 0;
                m_caretPositionOffset = 0;
                break;

            case SelectionMode.All:
                m_caretAnchor   = 0;
                extendSelection = true;
                goto fallthrough;

            case SelectionMode.Last:
fallthrough:
                m_caretPosition       = int.MaxValue;
                m_caretPositionOffset = 0;
                AlignCaretToNearestCluster(true, false);
                break;

            case SelectionMode.AbsoluteLeading:
                m_caretPosition       = advance;
                m_caretPositionOffset = 0;
                break;

            case SelectionMode.AbsoluteTrailing:
                m_caretPosition = advance;
                AlignCaretToNearestCluster(true, false);
                break;
            }

            absolutePosition = m_caretPosition + m_caretPositionOffset;

            if (!extendSelection)
            {
                m_caretAnchor = absolutePosition;
            }

            bool caretMoved = (absolutePosition != oldAbsolutePosition) ||
                              (m_caretAnchor != oldCaretAnchor);

            if (caretMoved)
            {
                // scroll the text automatically to avoid caret lost
                var lineMetrics = TextLayout.GetLineMetrics();
                int linePosition;
                GetLineFromPosition(lineMetrics, m_caretPosition, out line, out linePosition);

                if (line < TopLine)
                {
                    TopLine = line;
                }

                float visibleLines = TextLayout.Height / lineMetrics[0].Height;
                if (line > TopLine + visibleLines)
                {
                    TopLine = TopLine + 1;
                }
                //RectF rect;
                GetCaretRect();
                //UpdateSystemCaret(rect);
                //UpdateSelectionRange();
            }

            //Trace.TraceInformation("caretMoved {0} caretPosition  {1} caretPositionOffset {2} caretAnchor  {3} ",
            //                       caretMoved, m_caretPosition, m_caretPositionOffset, m_caretAnchor);
            Validate();
            return(caretMoved);
        }
示例#8
0
        public void Render()
        {
            RawVector2 nextPos;
            float      cursorY;

            lock (ScreenElementsLock) {
                cursorY = Math.Min(TextElementsHeight, Screen.ScreenSize.Height - (ScrollMode ? 0 : Base.DefaultTextFormat.FontSize));
                nextPos = new RawVector2(0, cursorY);
                float skipSz = 0;
                int   x;
                for (x = ScreenElements.Count - 1; skipSz < TextElementsScroll; x--)
                {
                    skipSz += ScreenElements[x].RenderedElementSize.Y;
                }

                var diff = TextElementsScroll - skipSz;
                nextPos.Y += diff;

                while (nextPos.Y > 0 && x >= 0)
                {
                    nextPos.Y -= ScreenElements[x].RenderedElementSize.Y;
                    ScreenElements[x].Render(nextPos);
                    x--;
                }
            }

            TextLayout tl;

            lock (CommandLock) {
                tl = new TextLayout(
                    Base.DWFactory,
                    CommandPre + Command,
                    Base.DefaultTextFormat,
                    Screen.ScreenSize.Width,
                    Screen.ScreenSize.Height
                    );

                Base.D2DRenderTarget.FillRectangle(new RawRectangleF(0, cursorY, tl.Metrics.Width, cursorY + tl.Metrics.Height), Base.Brushes.GetColor(Settings.Colors.Background));

                Base.D2DRenderTarget.DrawTextLayout(
                    new RawVector2(0, cursorY),
                    tl,
                    Base.Brushes.GetColor(Settings.Colors.NormalText),
                    DrawTextOptions.Clip
                    );
            }

            if (DateTime.Now.Millisecond % 200 >= 100 && !Commands.Parser.CommandExecuting)
            {
                float x, y;
                lock (CursorLock)
                    tl.HitTestTextPosition(Cursor + CommandPre.Length, new RawBool(false), out x, out y);

                //x = tl.Metrics.WidthIncludingTrailingWhitespace;

                Base.D2DRenderTarget.DrawLine(
                    new RawVector2(
                        x,
                        cursorY
                        ),
                    new RawVector2(
                        x,
                        cursorY + Base.DefaultTextFormat.FontSize
                        ),
                    Base.Brushes.GetColor(Settings.Colors.NormalText)
                    );
            }
            tl.Dispose();

            if (SpecialCommandPending || ScrollMode)
            {
                StrokeStyleProperties ssp = new StrokeStyleProperties();
                ssp.DashStyle  = DashStyle.Dash;
                ssp.DashOffset = 15.0f;
                using (var ss = new StrokeStyle(Base.D2DFactory, ssp))
                    Base.D2DRenderTarget.DrawRectangle(Screen.ScreenRect, Base.Brushes.GetColor(ScrollMode ? ConsoleColor.DarkBlue : ConsoleColor.DarkRed), 3.0f, ss);
            }
        }