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

@ -27,4 +27,4 @@ public class RelayCommand : ICommand
public bool CanExecute(object? parameter) => _canExecute(parameter); public bool CanExecute(object? parameter) => _canExecute(parameter);
public void Execute(object? parameter) => _execute(parameter); public void Execute(object? parameter) => _execute(parameter);
} }

View File

@ -12,7 +12,10 @@ public class Caret : Adorner
private readonly Brush _brush; private readonly Brush _brush;
private bool _blink; private bool _blink;
private Point _cursorPosition; 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) 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; _height = height;
_brush = brush; _brush = brush;
_pen = new Pen(brush, 1.5); _pen = new Pen(brush, 1.92);
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(428); _blinkTimer = new DispatcherTimer();
_timer.Tick += new EventHandler((s, e) => { _blinkTimer.Interval = TimeSpan.FromMilliseconds(428);
_blinkTimer.Tick += new EventHandler((s, e) => {
Toggle(_blink = !_blink); 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) public void Toggle(bool hide)
@ -34,13 +52,19 @@ public class Caret : Adorner
_pen.Brush = hide ? Brushes.Transparent : _brush; _pen.Brush = hide ? Brushes.Transparent : _brush;
} }
public void UpdateSelection((Point from, Point to)[] lines)
{
_lines = lines;
InvalidateVisual();
}
public void UpdatePosition(Point newPosition) public void UpdatePosition(Point newPosition)
{ {
_timer.Stop(); _cursorPosition = _targetPosition = newPosition;
Toggle(_blink = false); Toggle(_blink = false);
_cursorPosition = newPosition; //_blinkTimer.Stop();
//_moveTimer.Start();
InvalidateVisual(); InvalidateVisual();
_timer.Start();
} }
protected override void OnRender(DrawingContext drawingContext) protected override void OnRender(DrawingContext drawingContext)

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="CurrentSelection" Value="{Binding CurrentSelection}" />
<Setter Property="CurrentLineBuffer" Value="{Binding CurrentLineBuffer}" /> <Setter Property="CurrentLineBuffer" Value="{Binding CurrentLineBuffer}" />
</Style> </Style>
</UserControl.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.Editor.ViewModel;
using Qrakhen.Qamp.Memory; using Qrakhen.Qamp.Memory;
using System.Windows; using System.Windows;
@ -6,6 +8,7 @@ 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.Media;
using System.Windows.Shapes;
namespace Qrakhen.Qamp.Editor.Controls; namespace Qrakhen.Qamp.Editor.Controls;
@ -18,8 +21,22 @@ public partial class EditorFrame : UserControl
InitializeComponent(); InitializeComponent();
Loaded += OnLoaded; Loaded += OnLoaded;
PreviewKeyDown += OnKeyDown; 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) private void OnKeyDown(object sender, KeyEventArgs e)
{ {
if (DataContext is EditorFrameViewModel vm) { if (DataContext is EditorFrameViewModel vm) {
@ -30,8 +47,12 @@ public partial class EditorFrame : UserControl
Key.Down => new(1, 0), Key.Down => new(1, 0),
_ => new(0, 0) _ => new(0, 0)
}; };
if (position != BufferPosition.Void) if (position != BufferPosition.Initial)
vm.MoveCaretCommand.Execute(position); vm.MoveCaretCommand.Execute(
new MoveCaretOptions(
position,
true,
InputService.CtrlHeld));
else if (e.Key == Key.Back) else if (e.Key == Key.Back)
vm.DeleteCommand.Execute(-1); vm.DeleteCommand.Execute(-1);
else if (e.Key == Key.Delete) else if (e.Key == Key.Delete)
@ -41,9 +62,11 @@ public partial class EditorFrame : UserControl
else if (e.Key == Key.Delete) else if (e.Key == Key.Delete)
vm.DeleteCommand.Execute(1); vm.DeleteCommand.Execute(1);
else { else {
string c = e.Key.ToString(); char c = KeyHelper.Get(e.Key,
if (c.Length == 1) InputService.ShiftHeld ? 1 :
vm.InsertCommand.Execute(c); (InputService.CtrlHeld && InputService.AltHeld) ? 2 : 0);
if (c != 0)
vm.InsertCommand.Execute(c);
} }
e.Handled = true; e.Handled = true;
@ -63,7 +86,13 @@ public partial class EditorFrame : UserControl
Loaded -= OnLoaded; Loaded -= OnLoaded;
} }
private Point CalculateCaretPosition(int line, int index, double scrollY, double scrollX) 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); var ft = TextHelper.GetText(new string('_', index), 14, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
return new Point(ft.Width + scrollX + 2, line * TextHelper.LineHeight + scrollY); return new Point(ft.Width + scrollX + 2, line * TextHelper.LineHeight + scrollY);
@ -74,7 +103,12 @@ public partial class EditorFrame : UserControl
Caret?.UpdatePosition(point); Caret?.UpdatePosition(point);
} }
private void UpdateCaret() private void SelectRegion((Point from, Point to)[] lines)
{
Caret?.UpdateSelection(lines);
}
private void UpdateCaret()
{ {
int line = CursorPosition.Line; int line = CursorPosition.Line;
int charIndex = CursorPosition.Column; int charIndex = CursorPosition.Column;
@ -88,6 +122,10 @@ public partial class EditorFrame : UserControl
charIndex, charIndex,
scrollY, scrollY,
scrollX)); scrollX));
if (!CurrentSelection.IsVoid) {
}
} }
public LineBuffer CurrentLineBuffer { public LineBuffer CurrentLineBuffer {
@ -112,11 +150,29 @@ public partial class EditorFrame : UserControl
nameof(CursorPosition), nameof(CursorPosition),
typeof(BufferPosition), typeof(BufferPosition),
typeof(EditorFrame), typeof(EditorFrame),
new PropertyMetadata(BufferPosition.Void, OnCursorPositionChanged)); new PropertyMetadata(BufferPosition.Initial, OnCursorPositionChanged));
private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
var frame = (EditorFrame)d; var frame = (EditorFrame)d;
frame.UpdateCaret(); 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" mc:Ignorable="d"
d:DesignHeight="18" d:DesignWidth="420"> d:DesignHeight="18" d:DesignWidth="420">
<Grid> <Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -9,71 +9,114 @@ using System.Windows.Data;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Qrakhen.Qamp.Editor.Controls; namespace Qrakhen.Qamp.Editor.Controls;
public partial class LineRenderer : UserControl public partial class LineRenderer : UserControl
{ {
public static readonly DependencyProperty LineBufferProperty = 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)
{
base.OnRender(drawingContext);
if (string.IsNullOrEmpty(LineBuffer.Cached))
return;
var formattedText = TextHelper.GetText(
LineBuffer.Cached,
FontSize,
Brushes.White,
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( DependencyProperty.Register(
nameof(LineBuffer), nameof(LineBuffer),
typeof(LineBuffer), typeof(LineBuffer),
typeof(LineRenderer), typeof(LineRenderer),
new FrameworkPropertyMetadata(null, OnLineBufferChanged)); new FrameworkPropertyMetadata(null, OnLineBufferChanged));
public LineBuffer LineBuffer { public LineBuffer LineBuffer {
get => (LineBuffer)GetValue(LineBufferProperty); get => (LineBuffer)GetValue(LineBufferProperty);
set => SetValue(LineBufferProperty, value); set => SetValue(LineBufferProperty, value);
} }
private readonly Typeface _typeface = new(new FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal); private static void OnLineBufferChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
private const double FontSize = 14; {
private const double LineHeight = 18; if (d is LineRenderer renderer) {
if (e.OldValue is LineBuffer oldBuffer && oldBuffer != null) {
oldBuffer.PropertyChanged -= renderer.OnLineBufferPropertyChanged;
}
public LineRenderer() if (e.NewValue is LineBuffer newBuffer && newBuffer != null) {
{ newBuffer.PropertyChanged += renderer.OnLineBufferPropertyChanged;
Height = LineHeight; }
SnapsToDevicePixels = true;
}
protected override void OnRender(DrawingContext drawingContext) renderer.InvalidateVisual();
{ }
base.OnRender(drawingContext); }
if (string.IsNullOrEmpty(LineBuffer.Cached)) private void OnLineBufferPropertyChanged(object? sender, PropertyChangedEventArgs e)
return; {
if (e.PropertyName == nameof(LineBuffer.Cached))
InvalidateVisual();
}
var formattedText = TextHelper.GetText(
LineBuffer.Cached,
FontSize,
Brushes.White,
VisualTreeHelper.GetDpi(this).PixelsPerDip
);
drawingContext.DrawText(formattedText, new Point(2, 0));
}
private static void OnLineBufferChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) public static readonly DependencyProperty SelectionProperty =
{ DependencyProperty.Register(
if (d is LineRenderer renderer) { nameof(Selection),
if (e.OldValue is LineBuffer oldBuffer && oldBuffer != null) { typeof(BufferSpan),
oldBuffer.PropertyChanged -= renderer.OnLineBufferPropertyChanged; typeof(LineRenderer),
} new FrameworkPropertyMetadata(BufferSpan.None, OnSelectionChanged));
if (e.NewValue is LineBuffer newBuffer && newBuffer != null) { public BufferSpan Selection {
newBuffer.PropertyChanged += renderer.OnLineBufferPropertyChanged; get => (BufferSpan)GetValue(SelectionProperty);
} set => SetValue(SelectionProperty, value);
}
renderer.InvalidateVisual(); 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();
}
}
private void OnLineBufferPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(LineBuffer.Cached))
InvalidateVisual();
}
} }

View File

@ -1,9 +1,19 @@
using System; using System;
using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Runtime.ConstrainedExecution;
using System.Security.Policy;
using System.Text; using System.Text;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input; 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 public interface IInputHandler
{ {
@ -34,7 +44,7 @@ public class InputService : IInputHandler
public static bool LeftShiftHeld => Keyboard.IsKeyDown(Key.LeftShift); public static bool LeftShiftHeld => Keyboard.IsKeyDown(Key.LeftShift);
public static bool LeftAltHeld => Keyboard.IsKeyDown(Key.LeftAlt); public static bool LeftAltHeld => Keyboard.IsKeyDown(Key.LeftAlt);
public static bool RightCtrlHeld => Keyboard.IsKeyDown(Key.RightCtrl); 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 RightAltHeld => Keyboard.IsKeyDown(Key.RightAlt);
public static bool CtrlHeld => LeftCtrlHeld || RightCtrlHeld; public static bool CtrlHeld => LeftCtrlHeld || RightCtrlHeld;
public static bool ShiftHeld => LeftShiftHeld || RightShiftHeld; public static bool ShiftHeld => LeftShiftHeld || RightShiftHeld;
@ -44,4 +54,212 @@ 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 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 Left = new(0, -1);
public static readonly BufferPosition Right = new(0, 1); public static readonly BufferPosition Right = new(0, 1);
public static readonly BufferPosition Up = new(-1, 0); public static readonly BufferPosition Up = new(-1, 0);

View File

@ -2,6 +2,10 @@
public struct BufferRegion(BufferPosition from, BufferPosition to) 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 From = from;
public BufferPosition To = to; public BufferPosition To = to;
} }

View File

@ -1,123 +1,181 @@
using Qrakhen.Qamp.Editor.Commands; using Qrakhen.Qamp.Editor.Commands;
using Qrakhen.Qamp.Editor.Primitives; using Qrakhen.Qamp.Editor.Primitives;
using Qrakhen.Qamp.Memory; using Qrakhen.Qamp.Memory;
using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
namespace Qrakhen.Qamp.Editor.ViewModel; namespace Qrakhen.Qamp.Editor.ViewModel;
public class EditorFrameViewModel : ObservableObject public class EditorFrameViewModel : ObservableObject
{ {
private BufferPosition _cursorPosition; private BufferPosition _cursorPosition;
public BufferPosition CursorPosition { public BufferPosition CursorPosition {
get => _cursorPosition; get => _cursorPosition;
set => SetProperty(ref _cursorPosition, value); set => SetProperty(ref _cursorPosition, value);
} }
public int CursorLine => _cursorPosition.Line; private BufferRegion _currentSelection;
public BufferRegion CurrentSelection {
get => _currentSelection;
set => SetProperty(ref _currentSelection, value);
}
public int CursorColumn => _cursorPosition.Column; public int CursorLine => _cursorPosition.Line;
public LineBuffer CurrentLineBuffer => Lines[CursorLine]; public int CursorColumn => _cursorPosition.Column;
public ICommand MoveCaretCommand { get; } public LineBuffer CurrentLineBuffer => Lines[CursorLine];
public ICommand MoveBlockCommand { get; }
public ICommand InsertCommand { get; }
public ICommand DeleteCommand { get; }
public ICommand ClipBoardCommand { get; }
public ObservableCollection<LineBuffer> Lines { get; private set; } = new() { public ICommand MoveCaretCommand { get; }
new LineBuffer("test"), public ICommand MoveBlockCommand { get; }
new LineBuffer(" is very good;"), public ICommand InsertCommand { get; }
new LineBuffer(" jaja();"), public ICommand DeleteCommand { get; }
new LineBuffer("}") public ICommand ClipBoardCommand { get; }
};
public EditorFrameViewModel() public ObservableCollection<LineBuffer> Lines { get; private set; } = new() {
{ new LineBuffer("test"),
MoveCaretCommand = new RelayCommand(ExecuteMoveCaret); new LineBuffer(" is very good;"),
MoveBlockCommand = new RelayCommand(ExecuteMoveBlock); new LineBuffer(" jaja();"),
InsertCommand = new RelayCommand(ExecuteInsert); new LineBuffer("}")
DeleteCommand = new RelayCommand(ExecuteDelete); };
ClipBoardCommand = new RelayCommand(() => { });
}
private BufferPosition AlignBufferPosition(BufferPosition position) public EditorFrameViewModel()
{ {
int line = Math.Max(0, Math.Min(position.Line, Lines.Count - 1)); MoveCaretCommand = new RelayCommand(ExecuteMoveCaret);
LineBuffer buffer = Lines[line]; MoveBlockCommand = new RelayCommand(ExecuteMoveBlock);
int column = Math.Max(0, Math.Min(position.Column, buffer.Tail)); InsertCommand = new RelayCommand(ExecuteInsert);
return new BufferPosition(line, column); DeleteCommand = new RelayCommand(ExecuteDelete);
} ClipBoardCommand = new RelayCommand(() => { });
}
public void SetCursor(BufferPosition position)
{
CursorPosition = AlignBufferPosition(position);
OnPropertyChanged(nameof(CurrentLineBuffer));
}
public void MoveCursor(BufferPosition relative) public void SetCursor(int line, int column) => SetCursor(new BufferPosition(line, column));
{ public void SetCursor(BufferPosition position)
SetCursor(CursorPosition + relative); {
OnPropertyChanged(nameof(CurrentLineBuffer)); CursorPosition = AlignBufferPosition(position);
} OnPropertyChanged(nameof(CurrentLineBuffer));
}
public void Delete(BufferRegion region, bool follow = true) public void MoveCursor(int lines, int columns) => MoveCursor(new BufferPosition(lines, columns));
{ public void MoveCursor(BufferPosition relative, bool jump = false)
if (follow) {
SetCursor(new BufferPosition(region.From.Line, region.From.Column)); SetCursor(CursorPosition + relative);
for (int line = region.From.Line; line < region.To.Line; line++) OnPropertyChanged(nameof(CurrentLineBuffer));
{ }
} public void Delete(BufferRegion region, bool follow = true)
} {
public void Delete(int count = 1, bool follow = true)
{
if (follow)
MoveCursor(new BufferPosition(0, -count));
CurrentLineBuffer.Delete(CursorColumn, count);
}
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 {
if (follow) if (follow)
MoveCursor(new BufferPosition(0, data.Length)); SetCursor(new BufferPosition(region.From.Line, region.From.Column));
} for (int line = region.From.Line; line < region.To.Line; line++) {
}
private void ExecuteMoveCaret(object? parameter) }
{ }
if (parameter is BufferPosition relative)
MoveCursor(relative);
}
private void ExecuteMoveBlock(object? parameter) public void Delete(int count = 1, bool follow = true)
{ {
if (parameter is BufferPosition relative) if (count < 1)
MoveCursor(relative); return;
}
private void ExecuteInsert(object? parameter) if (follow) {
{ // remove newline
if (parameter is string str) if (CursorColumn - count < 0 && CursorLine > 0) {
Insert(str, true); 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;
}
}
private void ExecuteDelete(object? parameter) CurrentLineBuffer.Delete(CursorColumn, count);
{ }
if (parameter is int count)
Delete(Math.Abs(count), count < 0); /*
if (parameter is BufferRegion region)
Delete(region); asdasdy
} klöklöklökl
köäläälööx
cyxc
asdasd
* */
public void Insert(string data, bool follow = true)
{
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, 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 MoveCaretOptions options) {
if (options.Relative)
MoveCursor(options.Position, options.Jump);
else
SetCursor(options.Position);
}
}
private void ExecuteMoveBlock(object? parameter)
{
if (parameter is BufferPosition relative)
MoveCursor(relative);
}
private void ExecuteInsert(object? parameter)
{
if (parameter is string str)
Insert(str, true);
if (parameter is char c)
Insert(c + "", true);
}
private void ExecuteDelete(object? parameter)
{
if (parameter is int count)
Delete(Math.Abs(count), count < 0);
if (parameter is BufferRegion region)
Delete(region);
}
#endregion
} }

View File

@ -3,7 +3,33 @@ using System.Text;
namespace Qrakhen.Qamp.Memory; 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 Encoding Encoding { get; }
public string Cached { get; private set; } = ""; public string Cached { get; private set; } = "";
@ -32,6 +58,14 @@ public sealed class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
public void Insert(int cursor, string data) public void Insert(int cursor, string data)
=> Insert(cursor, Encoding.GetBytes(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() public override string ToString()
{ {
if (Tail == 0 || Encoding == null) if (Tail == 0 || Encoding == null)

View File

@ -13,12 +13,12 @@ public abstract class TailBuffer<T> : ICloneable
public TailBuffer(int size = 0x80) public TailBuffer(int size = 0x80)
{ {
_data = new T[size]; _data = new T[size];
} }
public TailBuffer(T[] data) 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); Array.Copy(data, 0, _data, 0, data.Length);
SetTail(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); from = SeekTail(from);
if (position == Tail) if (from == Tail)
return []; return [];
T[] data = _data[position..Tail]; until = until < 0 ? Tail : SeekTail(until);
Delete(position, Tail - position); T[] data = _data[from..until];
return data; Delete(from, until - from);
} return data;
}
public abstract object Clone(); public abstract object Clone();
// never write beyond tail (there might be invalid data) // 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) private void Prepare(int index, int count)
{ {