From 69ef06afd93651a6897f4cf21f2873a2fe154de8 Mon Sep 17 00:00:00 2001 From: Qrakhen Date: Wed, 19 Nov 2025 23:18:38 +0100 Subject: [PATCH] writing in the text editor? easy! --- Qrakhen.Qamp.Editor/Controls/Caret.cs | 39 +++++++++++++------ Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml | 1 + .../Controls/EditorFrame.xaml.cs | 38 +++++++++++++++--- .../Controls/LineRenderer.xaml.cs | 29 +++++++++++--- .../Primitives/CursorPosition.cs | 4 +- Qrakhen.Qamp.Editor/TextHelper.cs | 29 ++++++++++++++ .../ViewModel/EditorFrameViewModel.cs | 13 ++++++- Qrakhen.Qamp.Memory/LineBuffer.cs | 7 +++- Qrakhen.Qamp.Memory/TailBuffer.cs | 4 +- 9 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 Qrakhen.Qamp.Editor/TextHelper.cs diff --git a/Qrakhen.Qamp.Editor/Controls/Caret.cs b/Qrakhen.Qamp.Editor/Controls/Caret.cs index 7643fd9..99e424a 100644 --- a/Qrakhen.Qamp.Editor/Controls/Caret.cs +++ b/Qrakhen.Qamp.Editor/Controls/Caret.cs @@ -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); } } diff --git a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml index 295e860..d2641cf 100644 --- a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml +++ b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml @@ -12,6 +12,7 @@ diff --git a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs index 5be395b..a4701c3 100644 --- a/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs +++ b/Qrakhen.Qamp.Editor/Controls/EditorFrame.xaml.cs @@ -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; @@ -20,7 +23,7 @@ public partial class EditorFrame : UserControl private void OnKeyDown(object sender, KeyEventArgs e) { - if (DataContext is EditorFrameViewModel vm) { + if (DataContext is EditorFrameViewModel vm) { CursorPosition position = e.Key switch { Key.Right => new(0, 1), Key.Left => new(0, -1), @@ -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); diff --git a/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs index 3fa25c2..2e09e37 100644 --- a/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs +++ b/Qrakhen.Qamp.Editor/Controls/LineRenderer.xaml.cs @@ -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(); + } } diff --git a/Qrakhen.Qamp.Editor/Primitives/CursorPosition.cs b/Qrakhen.Qamp.Editor/Primitives/CursorPosition.cs index 577c49b..4dce632 100644 --- a/Qrakhen.Qamp.Editor/Primitives/CursorPosition.cs +++ b/Qrakhen.Qamp.Editor/Primitives/CursorPosition.cs @@ -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); diff --git a/Qrakhen.Qamp.Editor/TextHelper.cs b/Qrakhen.Qamp.Editor/TextHelper.cs new file mode 100644 index 0000000..7c7c8b2 --- /dev/null +++ b/Qrakhen.Qamp.Editor/TextHelper.cs @@ -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 + ); + } +} \ No newline at end of file diff --git a/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs b/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs index 0a566ba..cf4d346 100644 --- a/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs +++ b/Qrakhen.Qamp.Editor/ViewModel/EditorFrameViewModel.cs @@ -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)); } } diff --git a/Qrakhen.Qamp.Memory/LineBuffer.cs b/Qrakhen.Qamp.Memory/LineBuffer.cs index 0a8192f..6bb4e21 100644 --- a/Qrakhen.Qamp.Memory/LineBuffer.cs +++ b/Qrakhen.Qamp.Memory/LineBuffer.cs @@ -1,11 +1,13 @@ -using System.Text; +using System.ComponentModel; +using System.Text; namespace Qrakhen.Qamp.Memory; -public sealed class LineBuffer : TailBuffer +public sealed class LineBuffer : TailBuffer, 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 { base.SetTail(tail); Cached = ToString(); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Cached))); } public void Insert(int cursor, string data) diff --git a/Qrakhen.Qamp.Memory/TailBuffer.cs b/Qrakhen.Qamp.Memory/TailBuffer.cs index 00b24e1..0acb6f9 100644 --- a/Qrakhen.Qamp.Memory/TailBuffer.cs +++ b/Qrakhen.Qamp.Memory/TailBuffer.cs @@ -60,9 +60,9 @@ public abstract class TailBuffer 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;