cool tekst editor

This commit is contained in:
Qrakhen 2025-11-21 00:44:19 +01:00
parent 87ea8f4771
commit 30157f1f5d
13 changed files with 627 additions and 180 deletions

View File

@ -0,0 +1,7 @@
using Qrakhen.Qamp.Editor.Primitives;
namespace Qrakhen.Qamp.Editor.Commands;
public readonly record struct MoveCaretOptions(BufferPosition Position,
bool Relative = true,
bool Jump = false);

View File

@ -12,7 +12,10 @@ public class Caret : Adorner
private readonly Brush _brush;
private bool _blink;
private Point _cursorPosition;
private DispatcherTimer _timer;
private Point _targetPosition;
private (Point from, Point to)[] _lines = [];
private DispatcherTimer _blinkTimer;
private DispatcherTimer _moveTimer;
public Caret(UIElement adornedElement, Brush brush, double height = 18, double thickness = 1.72) : base(adornedElement)
{
@ -20,13 +23,28 @@ public class Caret : Adorner
_height = height;
_brush = brush;
_pen = new Pen(brush, 1.5);
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(428);
_timer.Tick += new EventHandler((s, e) => {
_pen = new Pen(brush, 1.92);
_blinkTimer = new DispatcherTimer();
_blinkTimer.Interval = TimeSpan.FromMilliseconds(428);
_blinkTimer.Tick += new EventHandler((s, e) => {
Toggle(_blink = !_blink);
});
_timer.Start();
_blinkTimer.Start();
_moveTimer = new DispatcherTimer();
_moveTimer.Interval = TimeSpan.FromMilliseconds(12);
_moveTimer.Tick += new EventHandler((s, e) => {
var delta = _targetPosition - _cursorPosition;
if (delta.Length > 2.4)
_cursorPosition += delta * .84;
else {
_cursorPosition = _targetPosition;
_moveTimer.Stop();
_blinkTimer.Start();
}
InvalidateVisual();
});
}
public void Toggle(bool hide)
@ -34,13 +52,19 @@ public class Caret : Adorner
_pen.Brush = hide ? Brushes.Transparent : _brush;
}
public void UpdateSelection((Point from, Point to)[] lines)
{
_lines = lines;
InvalidateVisual();
}
public void UpdatePosition(Point newPosition)
{
_timer.Stop();
_cursorPosition = _targetPosition = newPosition;
Toggle(_blink = false);
_cursorPosition = newPosition;
//_blinkTimer.Stop();
//_moveTimer.Start();
InvalidateVisual();
_timer.Start();
}
protected override void OnRender(DrawingContext drawingContext)

View File

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

View File

@ -1,4 +1,6 @@
using Qrakhen.Qamp.Editor.Primitives;
using Qrakhen.Qamp.Editor.Commands;
using Qrakhen.Qamp.Editor.Input;
using Qrakhen.Qamp.Editor.Primitives;
using Qrakhen.Qamp.Editor.ViewModel;
using Qrakhen.Qamp.Memory;
using System.Windows;
@ -6,6 +8,7 @@ using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Qrakhen.Qamp.Editor.Controls;
@ -18,6 +21,20 @@ public partial class EditorFrame : UserControl
InitializeComponent();
Loaded += OnLoaded;
PreviewKeyDown += OnKeyDown;
PreviewMouseDown += OnMouseDown;
}
protected void OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed) {
if (DataContext is EditorFrameViewModel vm) {
vm.MoveCaretCommand.Execute(
new MoveCaretOptions(
CalculateCursorPosition(e.GetPosition(this), 0, 0),
false,
false));
}
}
}
private void OnKeyDown(object sender, KeyEventArgs e)
@ -30,8 +47,12 @@ public partial class EditorFrame : UserControl
Key.Down => new(1, 0),
_ => new(0, 0)
};
if (position != BufferPosition.Void)
vm.MoveCaretCommand.Execute(position);
if (position != BufferPosition.Initial)
vm.MoveCaretCommand.Execute(
new MoveCaretOptions(
position,
true,
InputService.CtrlHeld));
else if (e.Key == Key.Back)
vm.DeleteCommand.Execute(-1);
else if (e.Key == Key.Delete)
@ -41,8 +62,10 @@ public partial class EditorFrame : UserControl
else if (e.Key == Key.Delete)
vm.DeleteCommand.Execute(1);
else {
string c = e.Key.ToString();
if (c.Length == 1)
char c = KeyHelper.Get(e.Key,
InputService.ShiftHeld ? 1 :
(InputService.CtrlHeld && InputService.AltHeld) ? 2 : 0);
if (c != 0)
vm.InsertCommand.Execute(c);
}
@ -63,6 +86,12 @@ public partial class EditorFrame : UserControl
Loaded -= OnLoaded;
}
private BufferPosition CalculateCursorPosition(Point point, double scrollY, double scrollX)
{
var ft = TextHelper.GetText(new string('_', 1), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
return new BufferPosition((int)(point.Y / TextHelper.LineHeight), (int)(point.X / ft.Width));
}
private Point CalculateCaretPosition(int line, int index, double scrollY, double scrollX)
{
var ft = TextHelper.GetText(new string('_', index), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
@ -74,6 +103,11 @@ public partial class EditorFrame : UserControl
Caret?.UpdatePosition(point);
}
private void SelectRegion((Point from, Point to)[] lines)
{
Caret?.UpdateSelection(lines);
}
private void UpdateCaret()
{
int line = CursorPosition.Line;
@ -88,6 +122,10 @@ public partial class EditorFrame : UserControl
charIndex,
scrollY,
scrollX));
if (!CurrentSelection.IsVoid) {
}
}
public LineBuffer CurrentLineBuffer {
@ -112,11 +150,29 @@ public partial class EditorFrame : UserControl
nameof(CursorPosition),
typeof(BufferPosition),
typeof(EditorFrame),
new PropertyMetadata(BufferPosition.Void, OnCursorPositionChanged));
new PropertyMetadata(BufferPosition.Initial, OnCursorPositionChanged));
private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var frame = (EditorFrame)d;
frame.UpdateCaret();
}
public BufferRegion CurrentSelection {
get => (BufferRegion)GetValue(CurrentSelectionProperty);
set => SetValue(CurrentSelectionProperty, value);
}
public static readonly DependencyProperty CurrentSelectionProperty =
DependencyProperty.Register(
nameof(CurrentSelection),
typeof(BufferRegion),
typeof(EditorFrame),
new PropertyMetadata(BufferRegion.Void, OnCurrentSelectionChanged));
private static void OnCurrentSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var frame = (EditorFrame)d;
frame.UpdateCaret();
}
}

View File

@ -7,6 +7,5 @@
mc:Ignorable="d"
d:DesignHeight="18" d:DesignWidth="420">
<Grid>
</Grid>
</UserControl>

View File

@ -9,34 +9,23 @@ using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Qrakhen.Qamp.Editor.Controls;
public partial class LineRenderer : UserControl
{
public static readonly DependencyProperty LineBufferProperty =
DependencyProperty.Register(
nameof(LineBuffer),
typeof(LineBuffer),
typeof(LineRenderer),
new FrameworkPropertyMetadata(null, OnLineBufferChanged));
public LineBuffer LineBuffer {
get => (LineBuffer)GetValue(LineBufferProperty);
set => SetValue(LineBufferProperty, value);
}
private readonly Typeface _typeface = new(new FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
private const double FontSize = 14;
private const double LineHeight = 18;
public Pen Pen { get; set; }
public Brush Highlighter { get; set; }
public LineRenderer()
{
Height = LineHeight;
SnapsToDevicePixels = true;
Highlighter = new SolidColorBrush(Color.FromArgb(0xff, 0x24, 0x92, 0x72));
Pen = new Pen(Highlighter, 2);
}
protected override void OnRender(DrawingContext drawingContext)
@ -53,9 +42,33 @@ public partial class LineRenderer : UserControl
VisualTreeHelper.GetDpi(this).PixelsPerDip
);
var x = Random.Shared.Next(0, LineBuffer.Tail);
var s = new BufferSpan(x, Random.Shared.Next(x, LineBuffer.Tail));
Selection = s;
var highlighter = TextHelper.GetText(
new string(' ', Selection.Start) + new string('█', Selection.End - Selection.Start),
FontSize,
Highlighter,
VisualTreeHelper.GetDpi(this).PixelsPerDip
);
drawingContext.DrawText(highlighter, new Point(2, 0));
drawingContext.DrawText(formattedText, new Point(2, 0));
}
public static readonly DependencyProperty LineBufferProperty =
DependencyProperty.Register(
nameof(LineBuffer),
typeof(LineBuffer),
typeof(LineRenderer),
new FrameworkPropertyMetadata(null, OnLineBufferChanged));
public LineBuffer LineBuffer {
get => (LineBuffer)GetValue(LineBufferProperty);
set => SetValue(LineBufferProperty, value);
}
private static void OnLineBufferChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is LineRenderer renderer) {
@ -76,4 +89,34 @@ public partial class LineRenderer : UserControl
if (e.PropertyName == nameof(LineBuffer.Cached))
InvalidateVisual();
}
public static readonly DependencyProperty SelectionProperty =
DependencyProperty.Register(
nameof(Selection),
typeof(BufferSpan),
typeof(LineRenderer),
new FrameworkPropertyMetadata(BufferSpan.None, OnSelectionChanged));
public BufferSpan Selection {
get => (BufferSpan)GetValue(SelectionProperty);
set => SetValue(SelectionProperty, value);
}
private static void OnSelectionChanged(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();
}
}
}

View File

@ -1,9 +1,19 @@
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Runtime.ConstrainedExecution;
using System.Security.Policy;
using System.Text;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Xml;
using System.Xml.Linq;
using static System.Net.Mime.MediaTypeNames;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Qrakhen.Qamp.Editor.InputService;
namespace Qrakhen.Qamp.Editor.Input;
public interface IInputHandler
{
@ -34,7 +44,7 @@ public class InputService : IInputHandler
public static bool LeftShiftHeld => Keyboard.IsKeyDown(Key.LeftShift);
public static bool LeftAltHeld => Keyboard.IsKeyDown(Key.LeftAlt);
public static bool RightCtrlHeld => Keyboard.IsKeyDown(Key.RightCtrl);
public static bool RightShiftHeld => Keyboard.IsKeyDown(Key.LeftCtrl);
public static bool RightShiftHeld => Keyboard.IsKeyDown(Key.RightShift);
public static bool RightAltHeld => Keyboard.IsKeyDown(Key.RightAlt);
public static bool CtrlHeld => LeftCtrlHeld || RightCtrlHeld;
public static bool ShiftHeld => LeftShiftHeld || RightShiftHeld;
@ -45,3 +55,211 @@ public class InputService : IInputHandler
}
}
public static class KeyHelper
{
private static readonly Dictionary<Key, char[]> _chars = [];
public static char Get(Key key, int modifier = 0)
{
return _chars[key][modifier];
}
static KeyHelper()
{
for (Key i = Key.A; i <= Key.Z; i++) {
char c = i.ToString()[^1];
Add(i, char.ToLower(c), char.ToUpper(c));
}
_chars[Key.Q] = ['q', 'Q', '@'];
Add(Key.None);
Add(Key.Cancel);
Add(Key.Back);
Add(Key.Tab, '\t');
Add(Key.LineFeed);
Add(Key.Clear);
Add(Key.Enter, '\n');
Add(Key.Return);
Add(Key.Pause);
Add(Key.Capital);
Add(Key.CapsLock);
Add(Key.HangulMode);
Add(Key.KanaMode);
Add(Key.JunjaMode);
Add(Key.FinalMode);
Add(Key.HanjaMode);
Add(Key.KanjiMode);
Add(Key.Escape);
Add(Key.ImeConvert);
Add(Key.ImeNonConvert);
Add(Key.ImeAccept);
Add(Key.ImeModeChange);
Add(Key.Space, ' ');
Add(Key.PageUp);
Add(Key.Prior);
Add(Key.Next);
Add(Key.PageDown);
Add(Key.End);
Add(Key.Home);
Add(Key.Left);
Add(Key.Up);
Add(Key.Right);
Add(Key.Down);
Add(Key.Select);
Add(Key.Print);
Add(Key.Execute);
Add(Key.PrintScreen);
Add(Key.Snapshot);
Add(Key.Insert);
Add(Key.Delete);
Add(Key.Help);
Add(Key.LWin);
Add(Key.RWin);
Add(Key.Apps);
Add(Key.Sleep);
Add(Key.D0, '0', '=', '}');
Add(Key.D1, '1', '!');
Add(Key.D2, '2', '"', '²');
Add(Key.D3, '3', '§', '³');
Add(Key.D4, '4', '$');
Add(Key.D5, '5', '%');
Add(Key.D6, '6', '&');
Add(Key.D7, '7', '/', '{');
Add(Key.D8, '8', '(', '[');
Add(Key.D9, '9', ')', ']');
Add(Key.NumPad0, '0');
Add(Key.NumPad1, '1');
Add(Key.NumPad2, '2');
Add(Key.NumPad3, '3');
Add(Key.NumPad4, '4');
Add(Key.NumPad5, '5');
Add(Key.NumPad6, '6');
Add(Key.NumPad7, '7');
Add(Key.NumPad8, '8');
Add(Key.NumPad9, '9');
Add(Key.Multiply, '*');
Add(Key.Add, '+');
Add(Key.Separator, '.');
Add(Key.Subtract, '-');
Add(Key.Decimal, ',');
Add(Key.Divide, '/');
Add(Key.F1);
Add(Key.F2);
Add(Key.F3);
Add(Key.F4);
Add(Key.F5);
Add(Key.F6);
Add(Key.F7);
Add(Key.F8);
Add(Key.F9);
Add(Key.F10);
Add(Key.F11);
Add(Key.F12);
Add(Key.F13);
Add(Key.F14);
Add(Key.F15);
Add(Key.F16);
Add(Key.F17);
Add(Key.F18);
Add(Key.F19);
Add(Key.F20);
Add(Key.F21);
Add(Key.F22);
Add(Key.F23);
Add(Key.F24);
Add(Key.NumLock);
Add(Key.Scroll);
Add(Key.LeftShift);
Add(Key.RightShift);
Add(Key.LeftCtrl);
Add(Key.RightCtrl);
Add(Key.LeftAlt);
Add(Key.RightAlt);
Add(Key.BrowserBack);
Add(Key.BrowserForward);
Add(Key.BrowserRefresh);
Add(Key.BrowserStop);
Add(Key.BrowserSearch);
Add(Key.BrowserFavorites);
Add(Key.BrowserHome);
Add(Key.VolumeMute);
Add(Key.VolumeDown);
Add(Key.VolumeUp);
Add(Key.MediaNextTrack);
Add(Key.MediaPreviousTrack);
Add(Key.MediaStop);
Add(Key.MediaPlayPause);
Add(Key.LaunchMail);
Add(Key.SelectMedia);
Add(Key.LaunchApplication1);
Add(Key.LaunchApplication2);
Add(Key.Oem1, 'ü', 'Ü');
Add(Key.OemSemicolon, ',', ';');
Add(Key.OemPlus, '+', '*', '~');
Add(Key.OemComma, ',', ';');
Add(Key.OemMinus, '-', '_');
Add(Key.OemPeriod, '.', ':');
Add(Key.Oem2);
Add(Key.OemQuestion, '#', '\'');
Add(Key.Oem3, 'ö', 'Ö');
Add(Key.OemTilde);
Add(Key.AbntC1);
Add(Key.AbntC2);
Add(Key.Oem4);
Add(Key.OemOpenBrackets);
Add(Key.Oem5, '^', '°');
Add(Key.OemPipe);
Add(Key.Oem6, '´', '`');
Add(Key.OemCloseBrackets);
Add(Key.Oem7, 'ß', '?');
Add(Key.OemQuotes, 'ä', 'Ä');
Add(Key.Oem8);
Add(Key.Oem102);
Add(Key.OemBackslash, '<', '>', '|');
Add(Key.ImeProcessed);
Add(Key.System);
Add(Key.DbeAlphanumeric);
Add(Key.OemAttn);
Add(Key.DbeKatakana);
Add(Key.OemFinish);
Add(Key.DbeHiragana);
Add(Key.OemCopy);
Add(Key.DbeSbcsChar);
Add(Key.OemAuto);
Add(Key.DbeDbcsChar);
Add(Key.OemEnlw);
Add(Key.DbeRoman);
Add(Key.OemBackTab);
Add(Key.Attn);
Add(Key.DbeNoRoman);
Add(Key.CrSel);
Add(Key.DbeEnterWordRegisterMode);
Add(Key.DbeEnterImeConfigureMode);
Add(Key.ExSel);
Add(Key.DbeFlushString);
Add(Key.EraseEof);
Add(Key.DbeCodeInput);
Add(Key.Play);
Add(Key.DbeNoCodeInput);
Add(Key.Zoom);
Add(Key.DbeDetermineString);
Add(Key.NoName);
Add(Key.DbeEnterDialogConversionMode);
Add(Key.Pa1);
Add(Key.OemClear);
Add(Key.DeadCharProcessed);
}
private static void Add(Key key, params char[] chars)
{
char[] padded = new char[3];
for (int i = 0; i < chars.Length; i++)
padded[i] = chars[i];
if (_chars.ContainsKey(key) && _chars[key][0] > 0)
return;
_chars[key] = padded;
}
}

View File

@ -2,7 +2,9 @@
public readonly record struct BufferPosition(int Line = 0, int Column = 0)
{
public static readonly BufferPosition Void = new(0, 0);
public bool IsInitial => Line == 0 && Column == 0;
public static readonly BufferPosition Initial = new(0, 0);
public static readonly BufferPosition Left = new(0, -1);
public static readonly BufferPosition Right = new(0, 1);
public static readonly BufferPosition Up = new(-1, 0);

View File

@ -2,6 +2,10 @@
public struct BufferRegion(BufferPosition from, BufferPosition to)
{
public static readonly BufferRegion Void = new BufferRegion(default, default);
public bool IsVoid => (From - To).IsInitial;
public BufferPosition From = from;
public BufferPosition To = to;
}

View File

@ -1,7 +1,9 @@
using Qrakhen.Qamp.Editor.Commands;
using Qrakhen.Qamp.Editor.Primitives;
using Qrakhen.Qamp.Memory;
using System;
using System.Collections.ObjectModel;
using System.Windows.Documents;
using System.Windows.Input;
namespace Qrakhen.Qamp.Editor.ViewModel;
@ -14,6 +16,12 @@ public class EditorFrameViewModel : ObservableObject
set => SetProperty(ref _cursorPosition, value);
}
private BufferRegion _currentSelection;
public BufferRegion CurrentSelection {
get => _currentSelection;
set => SetProperty(ref _currentSelection, value);
}
public int CursorLine => _cursorPosition.Line;
public int CursorColumn => _cursorPosition.Column;
@ -42,21 +50,17 @@ public class EditorFrameViewModel : ObservableObject
ClipBoardCommand = new RelayCommand(() => { });
}
private BufferPosition AlignBufferPosition(BufferPosition position)
{
int line = Math.Max(0, Math.Min(position.Line, Lines.Count - 1));
LineBuffer buffer = Lines[line];
int column = Math.Max(0, Math.Min(position.Column, buffer.Tail));
return new BufferPosition(line, column);
}
public void SetCursor(int line, int column) => SetCursor(new BufferPosition(line, column));
public void SetCursor(BufferPosition position)
{
CursorPosition = AlignBufferPosition(position);
OnPropertyChanged(nameof(CurrentLineBuffer));
}
public void MoveCursor(BufferPosition relative)
public void MoveCursor(int lines, int columns) => MoveCursor(new BufferPosition(lines, columns));
public void MoveCursor(BufferPosition relative, bool jump = false)
{
SetCursor(CursorPosition + relative);
OnPropertyChanged(nameof(CurrentLineBuffer));
@ -66,39 +70,89 @@ public class EditorFrameViewModel : ObservableObject
{
if (follow)
SetCursor(new BufferPosition(region.From.Line, region.From.Column));
for (int line = region.From.Line; line < region.To.Line; line++)
{
for (int line = region.From.Line; line < region.To.Line; line++) {
}
}
public void Delete(int count = 1, bool follow = true)
{
if (follow)
MoveCursor(new BufferPosition(0, -count));
if (count < 1)
return;
if (follow) {
// remove newline
if (CursorColumn - count < 0 && CursorLine > 0) {
int position = Lines[CursorLine - 1].Tail;
Lines[CursorLine - 1].Insert(
position,
CurrentLineBuffer.Slice(0));
Lines.RemoveAt(CursorLine);
SetCursor(CursorLine - 1, position);
return;
} else
MoveCursor(0, -count);
} else {
if (CursorColumn + count >= CurrentLineBuffer.Tail &&
CursorLine < Lines.Count - 1) {
CurrentLineBuffer.Insert(
CurrentLineBuffer.Tail,
Lines[CursorLine + 1].Slice(0));
Lines.RemoveAt(CursorLine + 1);
return;
}
}
CurrentLineBuffer.Delete(CursorColumn, count);
}
/*
asdasdy
klöklöklökl
köäläälööx
cyxc
asdasd
* */
public void Insert(string data, bool follow = true)
{
CurrentLineBuffer.Insert(CursorColumn, data);
int newLine = data.IndexOf(Environment.NewLine);
if (newLine > -1) {
int position = CursorColumn - (data.Length - newLine + 1);
LineBuffer split = new LineBuffer(CurrentLineBuffer.Slice(position), CurrentLineBuffer.Encoding);
Lines.Insert(CursorLine, split);
SetCursor(new BufferPosition(CursorLine + 1, split.Tail));
} else {
data = data.Replace("\t", " ");
var lines = data.Split(Environment.NewLine, StringSplitOptions.None);
for (int i = 0; i < lines.Length; i++) {
if (i > 0) {
LineBuffer split = new LineBuffer(
CurrentLineBuffer.Slice(CursorColumn),
CurrentLineBuffer.Encoding);
Lines.Insert(CursorLine + 1, split);
SetCursor(new BufferPosition(CursorLine + 1, 0));
}
CurrentLineBuffer.Insert(CursorColumn, lines[i]);
if (follow)
MoveCursor(new BufferPosition(0, data.Length));
MoveCursor(new BufferPosition(0, lines[i].Length));
}
}
private BufferPosition AlignBufferPosition(BufferPosition position)
{
int line = Math.Max(0, Math.Min(position.Line, Lines.Count - 1));
LineBuffer buffer = Lines[line];
int column = Math.Max(0, Math.Min(position.Column, buffer.Tail));
return new BufferPosition(line, column);
}
#region Commands
private void ExecuteMoveCaret(object? parameter)
{
if (parameter is BufferPosition relative)
MoveCursor(relative);
if (parameter is MoveCaretOptions options) {
if (options.Relative)
MoveCursor(options.Position, options.Jump);
else
SetCursor(options.Position);
}
}
private void ExecuteMoveBlock(object? parameter)
@ -111,6 +165,8 @@ public class EditorFrameViewModel : ObservableObject
{
if (parameter is string str)
Insert(str, true);
if (parameter is char c)
Insert(c + "", true);
}
private void ExecuteDelete(object? parameter)
@ -120,4 +176,6 @@ public class EditorFrameViewModel : ObservableObject
if (parameter is BufferRegion region)
Delete(region);
}
#endregion
}

View File

@ -3,7 +3,33 @@ using System.Text;
namespace Qrakhen.Qamp.Memory;
public sealed class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
public struct BufferSpan
{
public static readonly BufferSpan None = new(0, 0);
public int Start { get; set; } = 0;
public int End { get; set; } = 0;
public bool HasSelection => Start - End != 0;
public BufferSpan(int start, int end)
{
Start = start;
End = end;
}
public BufferSpan Synchronized<T>(TailBuffer<T> source)
{
return new BufferSpan(Start, Math.Min(End, source.Tail));
}
public T[] GetSelectedRegion<T>(TailBuffer<T> source)
{
return source.Data[Start..End];
}
}
public class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
{
public Encoding Encoding { get; }
public string Cached { get; private set; } = "";
@ -32,6 +58,14 @@ public sealed class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
public void Insert(int cursor, string data)
=> Insert(cursor, Encoding.GetBytes(data));
public string ToString(BufferSpan span)
{
var synced = span.Synchronized(this);
if (!synced.HasSelection)
return string.Empty;
return Encoding.GetString(Data[synced.Start..synced.End]);
}
public override string ToString()
{
if (Tail == 0 || Encoding == null)

View File

@ -18,7 +18,7 @@ public abstract class TailBuffer<T> : ICloneable
public TailBuffer(T[] data)
{
_data = new T[data.Length * 2];
_data = new T[Math.Max(0x10, data.Length * 2)];
Array.Copy(data, 0, _data, 0, data.Length);
SetTail(data.Length);
}
@ -54,20 +54,21 @@ public abstract class TailBuffer<T> : ICloneable
}
}
public T[] Slice(int position)
public T[] Slice(int from = 0, int until = -1)
{
position = SeekTail(position);
if (position == Tail)
from = SeekTail(from);
if (from == Tail)
return [];
T[] data = _data[position..Tail];
Delete(position, Tail - position);
until = until < 0 ? Tail : SeekTail(until);
T[] data = _data[from..until];
Delete(from, until - from);
return data;
}
public abstract object Clone();
// never write beyond tail (there might be invalid data)
private int SeekTail(int cursor) => cursor > Tail ? Tail : cursor;
private int SeekTail(int cursor) => Math.Max(0, Math.Min(cursor, Tail));
private void Prepare(int index, int count)
{