some more manipulation features
This commit is contained in:
parent
69ef06afd9
commit
87ea8f4771
|
|
@ -6,7 +6,6 @@ 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.Threading;
|
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Editor.Controls;
|
namespace Qrakhen.Qamp.Editor.Controls;
|
||||||
|
|
||||||
|
|
@ -24,26 +23,27 @@ 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 {
|
BufferPosition position = e.Key switch {
|
||||||
Key.Right => new(0, 1),
|
Key.Right => new(0, 1),
|
||||||
Key.Left => new(0, -1),
|
Key.Left => new(0, -1),
|
||||||
Key.Up => new(-1, 0),
|
Key.Up => new(-1, 0),
|
||||||
Key.Down => new(1, 0),
|
Key.Down => new(1, 0),
|
||||||
_ => new(0, 0)
|
_ => new(0, 0)
|
||||||
};
|
};
|
||||||
if (position != CursorPosition.Void &&
|
if (position != BufferPosition.Void)
|
||||||
vm.MoveCaretCommand.CanExecute(position))
|
|
||||||
{
|
|
||||||
vm.MoveCaretCommand.Execute(position);
|
vm.MoveCaretCommand.Execute(position);
|
||||||
} else {
|
else if (e.Key == Key.Back)
|
||||||
if (e.Key == Key.Back) {
|
vm.DeleteCommand.Execute(-1);
|
||||||
vm.Delete(1);
|
else if (e.Key == Key.Delete)
|
||||||
} else {
|
vm.DeleteCommand.Execute(1);
|
||||||
|
else if (e.Key == Key.Enter)
|
||||||
|
vm.InsertCommand.Execute(Environment.NewLine);
|
||||||
|
else if (e.Key == Key.Delete)
|
||||||
|
vm.DeleteCommand.Execute(1);
|
||||||
|
else {
|
||||||
string c = e.Key.ToString();
|
string c = e.Key.ToString();
|
||||||
if (c.Length == 1) {
|
if (c.Length == 1)
|
||||||
vm.Insert(c);
|
vm.InsertCommand.Execute(c);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
|
@ -102,17 +102,17 @@ public partial class EditorFrame : UserControl
|
||||||
typeof(EditorFrame),
|
typeof(EditorFrame),
|
||||||
new PropertyMetadata(null));
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
public CursorPosition CursorPosition {
|
public BufferPosition CursorPosition {
|
||||||
get => (CursorPosition)GetValue(CursorPositionProperty);
|
get => (BufferPosition)GetValue(CursorPositionProperty);
|
||||||
set => SetValue(CursorPositionProperty, value);
|
set => SetValue(CursorPositionProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly DependencyProperty CursorPositionProperty =
|
public static readonly DependencyProperty CursorPositionProperty =
|
||||||
DependencyProperty.Register(
|
DependencyProperty.Register(
|
||||||
nameof(CursorPosition),
|
nameof(CursorPosition),
|
||||||
typeof(CursorPosition),
|
typeof(BufferPosition),
|
||||||
typeof(EditorFrame),
|
typeof(EditorFrame),
|
||||||
new PropertyMetadata(CursorPosition.Void, OnCursorPositionChanged));
|
new PropertyMetadata(BufferPosition.Void, OnCursorPositionChanged));
|
||||||
|
|
||||||
private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
private static void OnCursorPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Editor.InputService;
|
||||||
|
|
||||||
|
public interface IInputHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ActionId
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
Copy,
|
||||||
|
Paste,
|
||||||
|
Delete,
|
||||||
|
MoveUp,
|
||||||
|
MoveDown,
|
||||||
|
MoveRight,
|
||||||
|
MoveLeft,
|
||||||
|
Submit,
|
||||||
|
Close
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void KeyEventListener();
|
||||||
|
|
||||||
|
public class InputService : IInputHandler
|
||||||
|
{
|
||||||
|
public static bool LeftCtrlHeld => Keyboard.IsKeyDown(Key.LeftCtrl);
|
||||||
|
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 RightAltHeld => Keyboard.IsKeyDown(Key.RightAlt);
|
||||||
|
public static bool CtrlHeld => LeftCtrlHeld || RightCtrlHeld;
|
||||||
|
public static bool ShiftHeld => LeftShiftHeld || RightShiftHeld;
|
||||||
|
public static bool AltHeld => LeftAltHeld || RightAltHeld;
|
||||||
|
|
||||||
|
public static void AddKeyListener(Key key, params Key[] modifiers)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
namespace Qrakhen.Qamp.Editor.Primitives;
|
||||||
|
|
||||||
|
public readonly record struct BufferPosition(int Line = 0, int Column = 0)
|
||||||
|
{
|
||||||
|
public static readonly BufferPosition Void = 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);
|
||||||
|
public static readonly BufferPosition Down = new(1, 0);
|
||||||
|
|
||||||
|
public static BufferPosition operator +(BufferPosition left, BufferPosition right)
|
||||||
|
=> new(left.Line + right.Line, left.Column + right.Column);
|
||||||
|
|
||||||
|
public static BufferPosition operator -(BufferPosition left, BufferPosition right)
|
||||||
|
=> new(left.Line - right.Line, left.Column - right.Column);
|
||||||
|
|
||||||
|
public static BufferPosition operator *(BufferPosition left, int factor)
|
||||||
|
=> new(left.Line * factor, left.Column * factor);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Qrakhen.Qamp.Editor.Primitives;
|
||||||
|
|
||||||
|
public struct BufferRegion(BufferPosition from, BufferPosition to)
|
||||||
|
{
|
||||||
|
public BufferPosition From = from;
|
||||||
|
public BufferPosition To = to;
|
||||||
|
}
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Editor.Primitives;
|
|
||||||
|
|
||||||
public record struct CursorPosition(int line = 0, int column = 0)
|
|
||||||
{
|
|
||||||
public static readonly CursorPosition Void = new(0, 0);
|
|
||||||
public static readonly CursorPosition Left = new(0, -1);
|
|
||||||
public static readonly CursorPosition Right = new(0, 1);
|
|
||||||
public static readonly CursorPosition Up = new(-1, 0);
|
|
||||||
public static readonly CursorPosition Down = new(1, 0);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
public static CursorPosition operator -(CursorPosition left, CursorPosition right)
|
|
||||||
=> new(left.Line - right.Line, left.Column - right.Column);
|
|
||||||
|
|
||||||
public static CursorPosition operator *(CursorPosition left, int factor)
|
|
||||||
=> new(left.Line * factor, left.Column * factor);
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Editor.Primitives;
|
|
||||||
|
|
||||||
public struct CursorRegion(CursorPosition from, CursorPosition to)
|
|
||||||
{
|
|
||||||
public CursorPosition From = from;
|
|
||||||
public CursorPosition To = to;
|
|
||||||
}
|
|
||||||
|
|
@ -8,25 +8,23 @@ namespace Qrakhen.Qamp.Editor.ViewModel;
|
||||||
|
|
||||||
public class EditorFrameViewModel : ObservableObject
|
public class EditorFrameViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
private CursorPosition _cursorPosition;
|
private BufferPosition _cursorPosition;
|
||||||
public CursorPosition CursorPosition {
|
public BufferPosition CursorPosition {
|
||||||
get => _cursorPosition;
|
get => _cursorPosition;
|
||||||
set => SetProperty(ref _cursorPosition, value);
|
set => SetProperty(ref _cursorPosition, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int CursorLine {
|
public int CursorLine => _cursorPosition.Line;
|
||||||
get => _cursorPosition.Line;
|
|
||||||
set => SetProperty(ref _cursorPosition.Line, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CursorColumn {
|
public int CursorColumn => _cursorPosition.Column;
|
||||||
get => _cursorPosition.Column;
|
|
||||||
set => SetProperty(ref _cursorPosition.Column, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LineBuffer CurrentLineBuffer => Lines[CursorLine];
|
public LineBuffer CurrentLineBuffer => Lines[CursorLine];
|
||||||
|
|
||||||
public ICommand MoveCaretCommand { get; }
|
public ICommand MoveCaretCommand { get; }
|
||||||
|
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 ObservableCollection<LineBuffer> Lines { get; private set; } = new() {
|
||||||
new LineBuffer("test"),
|
new LineBuffer("test"),
|
||||||
|
|
@ -37,47 +35,89 @@ public class EditorFrameViewModel : ObservableObject
|
||||||
|
|
||||||
public EditorFrameViewModel()
|
public EditorFrameViewModel()
|
||||||
{
|
{
|
||||||
MoveCaretCommand = new RelayCommand(ExecuteMoveCaret, CanMoveCaret);
|
MoveCaretCommand = new RelayCommand(ExecuteMoveCaret);
|
||||||
|
MoveBlockCommand = new RelayCommand(ExecuteMoveBlock);
|
||||||
|
InsertCommand = new RelayCommand(ExecuteInsert);
|
||||||
|
DeleteCommand = new RelayCommand(ExecuteDelete);
|
||||||
|
ClipBoardCommand = new RelayCommand(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCursor(int line, int column)
|
private BufferPosition AlignBufferPosition(BufferPosition position)
|
||||||
{
|
{
|
||||||
line = line < 0 ? 0 : line >= Lines.Count ? Lines.Count - 1 : line;
|
int line = Math.Max(0, Math.Min(position.Line, Lines.Count - 1));
|
||||||
LineBuffer buffer = Lines[line];
|
LineBuffer buffer = Lines[line];
|
||||||
column = column < 0 ? 0 : column > buffer.Tail ? buffer.Tail : column;
|
int column = Math.Max(0, Math.Min(position.Column, buffer.Tail));
|
||||||
CursorPosition = new(line, column);
|
return new BufferPosition(line, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MoveCursor(int lines, int columns)
|
public void SetCursor(BufferPosition position)
|
||||||
{
|
{
|
||||||
SetCursor(CursorLine + lines, CursorColumn + columns);
|
CursorPosition = AlignBufferPosition(position);
|
||||||
OnPropertyChanged(nameof(CurrentLineBuffer));
|
OnPropertyChanged(nameof(CurrentLineBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanMoveCaret(object? relativeUpdate)
|
public void MoveCursor(BufferPosition relative)
|
||||||
{
|
{
|
||||||
return true;
|
SetCursor(CursorPosition + relative);
|
||||||
|
OnPropertyChanged(nameof(CurrentLineBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExecuteMoveCaret(object? relativeUpdate)
|
public void Delete(BufferRegion region, bool follow = true)
|
||||||
{
|
{
|
||||||
if (relativeUpdate is CursorPosition pos)
|
if (follow)
|
||||||
MoveCursor(pos.Line, pos.Column);
|
SetCursor(new BufferPosition(region.From.Line, region.From.Column));
|
||||||
|
for (int line = region.From.Line; line < region.To.Line; line++)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Delete(int count = 1, bool follow = true)
|
public void Delete(int count = 1, bool follow = true)
|
||||||
{
|
{
|
||||||
if (follow)
|
if (follow)
|
||||||
MoveCursor(0, -count);
|
MoveCursor(new BufferPosition(0, -count));
|
||||||
CurrentLineBuffer.Delete(CursorColumn, count);
|
CurrentLineBuffer.Delete(CursorColumn, count);
|
||||||
OnPropertyChanged(nameof(Lines));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Insert(string data, bool follow = true)
|
public void Insert(string data, bool follow = true)
|
||||||
{
|
{
|
||||||
CurrentLineBuffer.Insert(CursorColumn, data);
|
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(0, data.Length);
|
MoveCursor(new BufferPosition(0, data.Length));
|
||||||
OnPropertyChanged(nameof(Lines));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteMoveCaret(object? parameter)
|
||||||
|
{
|
||||||
|
if (parameter is BufferPosition relative)
|
||||||
|
MoveCursor(relative);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteDelete(object? parameter)
|
||||||
|
{
|
||||||
|
if (parameter is int count)
|
||||||
|
Delete(Math.Abs(count), count < 0);
|
||||||
|
if (parameter is BufferRegion region)
|
||||||
|
Delete(region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ public sealed class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
public LineBuffer(string data) : this(data, Encoding.ASCII) { }
|
public LineBuffer(string data) : this(data, Encoding.ASCII) { }
|
||||||
public LineBuffer(string data, Encoding encoding) : base(encoding.GetBytes(data))
|
public LineBuffer(string data, Encoding encoding) : this(encoding.GetBytes(data), encoding) { }
|
||||||
|
public LineBuffer(byte[] data, Encoding encoding) : base(data)
|
||||||
{
|
{
|
||||||
Encoding = encoding;
|
Encoding = encoding;
|
||||||
Cached = ToString();
|
Cached = ToString();
|
||||||
|
|
@ -38,5 +39,10 @@ public sealed class LineBuffer : TailBuffer<byte>, INotifyPropertyChanged
|
||||||
return Encoding.GetString(Data, 0, Tail);
|
return Encoding.GetString(Data, 0, Tail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override object Clone()
|
||||||
|
{
|
||||||
|
return new LineBuffer(_data.ToArray(), Encoding);
|
||||||
|
}
|
||||||
|
|
||||||
public static implicit operator LineBuffer(string s) => new LineBuffer(s);
|
public static implicit operator LineBuffer(string s) => new LineBuffer(s);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
namespace Qrakhen.Qamp.Memory;
|
namespace Qrakhen.Qamp.Memory;
|
||||||
|
|
||||||
// add to qrakhen.memory package for clout
|
// add to qrakhen.memory package for clout
|
||||||
public abstract class TailBuffer<T>
|
public abstract class TailBuffer<T> : ICloneable
|
||||||
{
|
{
|
||||||
private T[] _data;
|
protected T[] _data;
|
||||||
public T[] Data {
|
public T[] Data {
|
||||||
get => _data;
|
get => _data;
|
||||||
}
|
}
|
||||||
|
|
@ -54,6 +54,18 @@ public abstract class TailBuffer<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public T[] Slice(int position)
|
||||||
|
{
|
||||||
|
position = SeekTail(position);
|
||||||
|
if (position == Tail)
|
||||||
|
return [];
|
||||||
|
T[] data = _data[position..Tail];
|
||||||
|
Delete(position, Tail - position);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => cursor > Tail ? Tail : cursor;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue