writing in the text editor? easy!

This commit is contained in:
Qrakhen 2025-11-19 23:18:38 +01:00
parent 7d9e81c60f
commit 69ef06afd9
9 changed files with 136 additions and 28 deletions

View File

@ -1,35 +1,52 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Threading;
namespace Qrakhen.Qamp.Editor.Controls;
public class Caret : Adorner
{
private readonly Pen _caretPen;
private readonly double _lineHeight;
private readonly Pen _pen;
private readonly double _height;
private readonly Brush _brush;
private bool _blink;
private Point _cursorPosition;
private DispatcherTimer _timer;
public Caret(UIElement adornedElement, double lineHeight) : base(adornedElement)
public Caret(UIElement adornedElement, Brush brush, double height = 18, double thickness = 1.72) : base(adornedElement)
{
_lineHeight = lineHeight;
_caretPen = new Pen(Brushes.LimeGreen, 1.5);
IsHitTestVisible = false;
_height = height;
_brush = brush;
_pen = new Pen(brush, 1.5);
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(428);
_timer.Tick += new EventHandler((s, e) => {
Toggle(_blink = !_blink);
});
_timer.Start();
}
public void Toggle(bool hide)
{
_pen.Brush = hide ? Brushes.Transparent : _brush;
}
public void UpdatePosition(Point newPosition)
{
_timer.Stop();
Toggle(_blink = false);
_cursorPosition = newPosition;
InvalidateVisual();
_timer.Start();
}
protected override void OnRender(DrawingContext drawingContext)
{
Point startPoint = new(_cursorPosition.X, _cursorPosition.Y);
Point endPoint = new(_cursorPosition.X, _cursorPosition.Y + _lineHeight);
drawingContext.DrawLine(_caretPen, startPoint, endPoint);
Point endPoint = new(_cursorPosition.X, _cursorPosition.Y + _height);
drawingContext.DrawLine(_pen, startPoint, endPoint);
}
}

View File

@ -12,6 +12,7 @@
<UserControl.Style>
<Style TargetType="{x:Type local:EditorFrame}">
<Setter Property="CursorPosition" Value="{Binding CursorPosition}" />
<Setter Property="CurrentLineBuffer" Value="{Binding CurrentLineBuffer}" />
</Style>
</UserControl.Style>

View File

@ -1,9 +1,12 @@
using Qrakhen.Qamp.Editor.Primitives;
using Qrakhen.Qamp.Editor.ViewModel;
using Qrakhen.Qamp.Memory;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace Qrakhen.Qamp.Editor.Controls;
@ -28,10 +31,22 @@ public partial class EditorFrame : UserControl
Key.Down => new(1, 0),
_ => new(0, 0)
};
if (vm.MoveCaretCommand.CanExecute(position)) {
if (position != CursorPosition.Void &&
vm.MoveCaretCommand.CanExecute(position))
{
vm.MoveCaretCommand.Execute(position);
e.Handled = true;
} else {
if (e.Key == Key.Back) {
vm.Delete(1);
} else {
string c = e.Key.ToString();
if (c.Length == 1) {
vm.Insert(c);
}
}
}
e.Handled = true;
}
}
@ -40,7 +55,7 @@ public partial class EditorFrame : UserControl
Focus();
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (adornerLayer != null) {
Caret = new Caret(this, 18);
Caret = new Caret(this, Brushes.LimeGreen);
adornerLayer.Add(Caret);
} else {
System.Diagnostics.Debug.WriteLine("AdornerLayer still null. Check parent hierarchy.");
@ -50,7 +65,8 @@ public partial class EditorFrame : UserControl
private Point CalculateCaretPosition(int line, int index, double scrollY, double scrollX)
{
return new Point(10 * index + scrollX, line * 18 + scrollY);
var ft = TextHelper.GetText(new string('_', index), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
return new Point(ft.Width + scrollX + 2, line * TextHelper.LineHeight + scrollY);
}
private void MoveCaret(Point point)
@ -74,6 +90,18 @@ public partial class EditorFrame : UserControl
scrollX));
}
public LineBuffer CurrentLineBuffer {
get => (LineBuffer)GetValue(CurrentLineBufferProperty);
set => SetValue(CurrentLineBufferProperty, value);
}
public static readonly DependencyProperty CurrentLineBufferProperty =
DependencyProperty.Register(
nameof(CurrentLineBuffer),
typeof(LineBuffer),
typeof(EditorFrame),
new PropertyMetadata(null));
public CursorPosition CursorPosition {
get => (CursorPosition)GetValue(CursorPositionProperty);
set => SetValue(CursorPositionProperty, value);

View File

@ -1,6 +1,7 @@
using Qrakhen.Qamp.Memory;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
@ -21,7 +22,7 @@ public partial class LineRenderer : UserControl
nameof(LineBuffer),
typeof(LineBuffer),
typeof(LineRenderer),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
new FrameworkPropertyMetadata(null, OnLineBufferChanged));
public LineBuffer LineBuffer {
get => (LineBuffer)GetValue(LineBufferProperty);
@ -45,11 +46,8 @@ public partial class LineRenderer : UserControl
if (string.IsNullOrEmpty(LineBuffer.Cached))
return;
var formattedText = new FormattedText(
var formattedText = TextHelper.GetText(
LineBuffer.Cached,
System.Globalization.CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
_typeface,
FontSize,
Brushes.White,
VisualTreeHelper.GetDpi(this).PixelsPerDip
@ -57,4 +55,25 @@ public partial class LineRenderer : UserControl
drawingContext.DrawText(formattedText, new Point(2, 0));
}
private static void OnLineBufferChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is LineRenderer renderer) {
if (e.OldValue is LineBuffer oldBuffer && oldBuffer != null) {
oldBuffer.PropertyChanged -= renderer.OnLineBufferPropertyChanged;
}
if (e.NewValue is LineBuffer newBuffer && newBuffer != null) {
newBuffer.PropertyChanged += renderer.OnLineBufferPropertyChanged;
}
renderer.InvalidateVisual();
}
}
private void OnLineBufferPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(LineBuffer.Cached))
InvalidateVisual();
}
}

View File

@ -8,8 +8,8 @@ public record struct CursorPosition(int line = 0, int column = 0)
public static readonly CursorPosition Up = new(-1, 0);
public static readonly CursorPosition Down = new(1, 0);
public int Line = line < 0 ? 0 : line;
public int Column = column < 0 ? 0 : column;
public int Line = line;
public int Column = column;
public static CursorPosition operator +(CursorPosition left, CursorPosition right)
=> new(left.Line + right.Line, left.Column + right.Column);

View File

@ -0,0 +1,29 @@
using System.Windows;
using System.Windows.Media;
namespace Qrakhen.Qamp.Editor;
public static class TextHelper // temp
{
public static double Size { get; set; } = 14;
public static double LineHeight { get; set; } = 18;
public static Brush DefaultColor { get; set; } = Brushes.AntiqueWhite;
public static Typeface Typeface { get; set; } = new(
new FontFamily("Consolas"),
FontStyles.Normal,
FontWeights.Normal,
FontStretches.Normal);
public static FormattedText GetText(string text, double size, Brush? brush = null, double dpiScale = 1f)
{
return new FormattedText(
text,
System.Globalization.CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
Typeface,
size,
brush ?? DefaultColor,
dpiScale
);
}
}

View File

@ -51,6 +51,7 @@ public class EditorFrameViewModel : ObservableObject
public void MoveCursor(int lines, int columns)
{
SetCursor(CursorLine + lines, CursorColumn + columns);
OnPropertyChanged(nameof(CurrentLineBuffer));
}
public bool CanMoveCaret(object? relativeUpdate)
@ -64,9 +65,19 @@ public class EditorFrameViewModel : ObservableObject
MoveCursor(pos.Line, pos.Column);
}
public void Insert(string data)
public void Delete(int count = 1, bool follow = true)
{
if (follow)
MoveCursor(0, -count);
CurrentLineBuffer.Delete(CursorColumn, count);
OnPropertyChanged(nameof(Lines));
}
public void Insert(string data, bool follow = true)
{
CurrentLineBuffer.Insert(CursorColumn, data);
if (follow)
MoveCursor(0, data.Length);
OnPropertyChanged(nameof(Lines));
}
}

View File

@ -1,11 +1,13 @@
using System.Text;
using System.ComponentModel;
using System.Text;
namespace Qrakhen.Qamp.Memory;
public sealed class LineBuffer : TailBuffer<byte>
public sealed class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
{
public Encoding Encoding { get; }
public string Cached { get; private set; } = "";
public event PropertyChangedEventHandler? PropertyChanged;
public LineBuffer(Encoding encoding, int size = 0x80) : base(size)
{
@ -23,6 +25,7 @@ public sealed class LineBuffer : TailBuffer<byte>
{
base.SetTail(tail);
Cached = ToString();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Cached)));
}
public void Insert(int cursor, string data)

View File

@ -60,9 +60,9 @@ public abstract class TailBuffer<T>
private void Prepare(int index, int count)
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
if (index + count <= _data.Length)
if (count + Tail <= _data.Length)
return;
while (index + count > _data.Length) {
while (count + Tail > _data.Length) {
T[] grow = new T[_data.Length * 2];
Array.Copy(_data, grow, _data.Length);
_data = grow;