Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caret position breaks when an element longer than 1 text character is used in ElementGenerators #445

Open
KillzXGaming opened this issue Jul 31, 2024 · 0 comments

Comments

@KillzXGaming
Copy link

KillzXGaming commented Jul 31, 2024

Hello. I made a custom VisualLineElementGenerator where I draw a box similar to how the control character boxes are done.
The difference with mine, I use a longer length than 1 in FormattedTextElement constructor which seems to cause issues with the caret position as it advances much further out than it should be the more tags that are written before it.

The idea is I want to hide text longer than 1 character within [] brackets and create a custom visual for these.

image

A way to replicate this.

    public MessageEditor()
    {
        InitializeComponent();
        
        _textEditor.TextArea.TextView.ElementGenerators.Add(new ControlCodeElementGenerator(_textEditor.TextArea));
        _textEditor.Text = "Test [Color::Red][Text::Bold]aaa This text cannot be selected at the right pos[Text::Normal]Test";
    }

public class ControlCodeElementGenerator : VisualLineElementGenerator
{
    private readonly Regex _regex;
    private readonly TextArea _textArea;

    public ControlCodeElementGenerator(TextArea textArea)
    {
        _regex = new Regex(@"\[.*?\]"); //[] text are tags where we want to custom display
        _textArea = textArea;
    }

    public override int GetFirstInterestedOffset(int startOffset)
    {
        //get the placement of the a tag [] for constructing a visual element at the text place
        var text = CurrentContext.Document.Text;
        var match = _regex.Match(text, startOffset);

        return match.Success ? match.Index : -1;
    }

    public override VisualLineElement ConstructElement(int offset)
    {
        var text = CurrentContext.Document.Text;
        var match = _regex.Match(text, offset);

        if (match.Success && match.Index == offset) 
        {
            //constructs a box like the control character box
            var runProperties = new VisualLineElementTextRunProperties(CurrentContext.GlobalTextRunProperties);
            runProperties.SetForegroundBrush(Brushes.White);

            var controlCode = match.Value;

            var textLine = FormattedTextElement.PrepareText(
                TextFormatter.Current, "[Tag]", runProperties);

            return new SpecialCharacterBoxElement(textLine, controlCode.Length);  //only if length is 1, caret pos works correctly
        }

        return null;
    }
}

public class SpecialCharacterBoxElement : FormattedTextElement
{
    private int _length;

    public SpecialCharacterBoxElement(TextLine text, int length) : base(text, length) //todo only if length is 1, caret pos works correctly
    {
        _length = length;
    }

    public override int GetNextCaretPosition(int visualColumn, AvaloniaEdit.Document.LogicalDirection direction, CaretPositioningMode mode)
    {
        if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
            return base.GetNextCaretPosition(visualColumn, direction, mode);
        else
            return -1;
    }

    public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
    {
        return new SpecialCharacterTextRun(this, TextRunProperties);
    }
}

public class SpecialCharacterTextRun : FormattedTextRun
{
    private static readonly ISolidColorBrush DarkGrayBrush;

    internal const double BoxMargin = 3;

    static SpecialCharacterTextRun()
    {
        DarkGrayBrush = new ImmutableSolidColorBrush(Color.FromArgb(200, 128, 128, 128));
    }

    public SpecialCharacterTextRun(FormattedTextElement element, TextRunProperties properties)
        : base(element, properties)
    {
    }

    public override Size Size
    {
        get
        {
            var s = base.Size;

            return s.WithWidth(s.Width + BoxMargin);
        }
    }

    public override void Draw(DrawingContext drawingContext, Point origin)
    {
        var (x, y) = origin;

        var newOrigin = new Point(x + (BoxMargin / 2), y);

        var (width, height) = Size;

        var r = new Rect(x, y, width, height);

        drawingContext.FillRectangle(DarkGrayBrush, r, 2.5f);

        base.Draw(drawingContext, newOrigin);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant