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.Windows;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading;
namespace Qrakhen.Qamp.Editor.Controls; namespace Qrakhen.Qamp.Editor.Controls;
public class Caret : Adorner public class Caret : Adorner
{ {
private readonly Pen _caretPen; private readonly Pen _pen;
private readonly double _lineHeight; private readonly double _height;
private readonly Brush _brush;
private bool _blink;
private Point _cursorPosition; 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; 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) public void UpdatePosition(Point newPosition)
{ {
_timer.Stop();
Toggle(_blink = false);
_cursorPosition = newPosition; _cursorPosition = newPosition;
InvalidateVisual(); InvalidateVisual();
_timer.Start();
} }
protected override void OnRender(DrawingContext drawingContext) protected override void OnRender(DrawingContext drawingContext)
{ {
Point startPoint = new(_cursorPosition.X, _cursorPosition.Y); Point startPoint = new(_cursorPosition.X, _cursorPosition.Y);
Point endPoint = new(_cursorPosition.X, _cursorPosition.Y + _lineHeight); Point endPoint = new(_cursorPosition.X, _cursorPosition.Y + _height);
drawingContext.DrawLine(_caretPen, startPoint, endPoint); drawingContext.DrawLine(_pen, startPoint, endPoint);
} }
} }

View File

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

View File

@ -1,9 +1,12 @@
using Qrakhen.Qamp.Editor.Primitives; using Qrakhen.Qamp.Editor.Primitives;
using Qrakhen.Qamp.Editor.ViewModel; using Qrakhen.Qamp.Editor.ViewModel;
using Qrakhen.Qamp.Memory;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace Qrakhen.Qamp.Editor.Controls; namespace Qrakhen.Qamp.Editor.Controls;
@ -20,7 +23,7 @@ public partial class EditorFrame : UserControl
private void OnKeyDown(object sender, KeyEventArgs e) private void OnKeyDown(object sender, KeyEventArgs e)
{ {
if (DataContext is EditorFrameViewModel vm) { if (DataContext is EditorFrameViewModel vm) {
CursorPosition position = e.Key switch { CursorPosition position = e.Key switch {
Key.Right => new(0, 1), Key.Right => new(0, 1),
Key.Left => new(0, -1), Key.Left => new(0, -1),
@ -28,10 +31,22 @@ public partial class EditorFrame : UserControl
Key.Down => new(1, 0), Key.Down => new(1, 0),
_ => new(0, 0) _ => new(0, 0)
}; };
if (vm.MoveCaretCommand.CanExecute(position)) { if (position != CursorPosition.Void &&
vm.MoveCaretCommand.CanExecute(position))
{
vm.MoveCaretCommand.Execute(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(); Focus();
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this); AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (adornerLayer != null) { if (adornerLayer != null) {
Caret = new Caret(this, 18); Caret = new Caret(this, Brushes.LimeGreen);
adornerLayer.Add(Caret); adornerLayer.Add(Caret);
} else { } else {
System.Diagnostics.Debug.WriteLine("AdornerLayer still null. Check parent hierarchy."); 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) 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) private void MoveCaret(Point point)
@ -74,6 +90,18 @@ public partial class EditorFrame : UserControl
scrollX)); 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 { public CursorPosition CursorPosition {
get => (CursorPosition)GetValue(CursorPositionProperty); get => (CursorPosition)GetValue(CursorPositionProperty);
set => SetValue(CursorPositionProperty, value); set => SetValue(CursorPositionProperty, value);

View File

@ -1,6 +1,7 @@
using Qrakhen.Qamp.Memory; using Qrakhen.Qamp.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@ -21,7 +22,7 @@ public partial class LineRenderer : UserControl
nameof(LineBuffer), nameof(LineBuffer),
typeof(LineBuffer), typeof(LineBuffer),
typeof(LineRenderer), typeof(LineRenderer),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); new FrameworkPropertyMetadata(null, OnLineBufferChanged));
public LineBuffer LineBuffer { public LineBuffer LineBuffer {
get => (LineBuffer)GetValue(LineBufferProperty); get => (LineBuffer)GetValue(LineBufferProperty);
@ -45,11 +46,8 @@ public partial class LineRenderer : UserControl
if (string.IsNullOrEmpty(LineBuffer.Cached)) if (string.IsNullOrEmpty(LineBuffer.Cached))
return; return;
var formattedText = new FormattedText( var formattedText = TextHelper.GetText(
LineBuffer.Cached, LineBuffer.Cached,
System.Globalization.CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
_typeface,
FontSize, FontSize,
Brushes.White, Brushes.White,
VisualTreeHelper.GetDpi(this).PixelsPerDip VisualTreeHelper.GetDpi(this).PixelsPerDip
@ -57,4 +55,25 @@ public partial class LineRenderer : UserControl
drawingContext.DrawText(formattedText, new Point(2, 0)); 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 Up = new(-1, 0);
public static readonly CursorPosition Down = new(1, 0); public static readonly CursorPosition Down = new(1, 0);
public int Line = line < 0 ? 0 : line; public int Line = line;
public int Column = column < 0 ? 0 : column; public int Column = column;
public static CursorPosition operator +(CursorPosition left, CursorPosition right) public static CursorPosition operator +(CursorPosition left, CursorPosition right)
=> new(left.Line + right.Line, left.Column + right.Column); => 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) public void MoveCursor(int lines, int columns)
{ {
SetCursor(CursorLine + lines, CursorColumn + columns); SetCursor(CursorLine + lines, CursorColumn + columns);
OnPropertyChanged(nameof(CurrentLineBuffer));
} }
public bool CanMoveCaret(object? relativeUpdate) public bool CanMoveCaret(object? relativeUpdate)
@ -64,9 +65,19 @@ public class EditorFrameViewModel : ObservableObject
MoveCursor(pos.Line, pos.Column); 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); CurrentLineBuffer.Insert(CursorColumn, data);
if (follow)
MoveCursor(0, data.Length);
OnPropertyChanged(nameof(Lines)); OnPropertyChanged(nameof(Lines));
} }
} }

View File

@ -1,11 +1,13 @@
using System.Text; using System.ComponentModel;
using System.Text;
namespace Qrakhen.Qamp.Memory; namespace Qrakhen.Qamp.Memory;
public sealed class LineBuffer : TailBuffer<byte> public sealed class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
{ {
public Encoding Encoding { get; } public Encoding Encoding { get; }
public string Cached { get; private set; } = ""; public string Cached { get; private set; } = "";
public event PropertyChangedEventHandler? PropertyChanged;
public LineBuffer(Encoding encoding, int size = 0x80) : base(size) public LineBuffer(Encoding encoding, int size = 0x80) : base(size)
{ {
@ -23,6 +25,7 @@ public sealed class LineBuffer : TailBuffer<byte>
{ {
base.SetTail(tail); base.SetTail(tail);
Cached = ToString(); Cached = ToString();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Cached)));
} }
public void Insert(int cursor, string data) 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) private void Prepare(int index, int count)
{ {
ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfNegative(index);
if (index + count <= _data.Length) if (count + Tail <= _data.Length)
return; return;
while (index + count > _data.Length) { while (count + Tail > _data.Length) {
T[] grow = new T[_data.Length * 2]; T[] grow = new T[_data.Length * 2];
Array.Copy(_data, grow, _data.Length); Array.Copy(_data, grow, _data.Length);
_data = grow; _data = grow;